一。关于终止线程stop与interrupt
一般来说,线程执行结束后就变成消亡状态,乍看之下我们并不需要人为进行干预(人为停止线程),不过凡事都有例外吧,在服务器或者其他应用场景下,线程为了提供服务而一直在不停的运转,因此必要时刻我们还需“人为干涉的”。
通常情况下,终止线程有两种方式:stop与interrupt
1) stop:暴力的停止线程(不管线程执行到哪段代码,立刻干掉),这个方法因为过于暴力会导致安全问题,因此JDK不推荐使用。
2) interrupt:优雅停止,调用该方法会通知线程可以进行停止操作了,此时线程只是变成可停止状态(thread.isInterrupted的值为true),实际上并没有停止
请看下段代码:
1 package com.bdqn.lyrk.basic; 2 3 /** 4 * 一个线程设置共享变量的值,保持ID与name值相同 5 * 另外一个线程读取共享变量的值,发现ID与name值不同时打印 6 * 7 * @author chen.nie 8 * @date 2018/1/30 9 **/ 10 public class StopThreadUnsafe { 11 12 public static User user = new User(); 13 14 public static class User { 15 private int id; 16 private String name; 17 18 public int getId() { 19 return id; 20 } 21 22 public void setId(int id) { 23 this.id = id; 24 } 25 26 public String getName() { 27 return name; 28 } 29 30 public void setName(String name) { 31 this.name = name; 32 } 33 34 public User() { 35 id = 0; 36 name = "0"; 37 } 38 39 @Override 40 public String toString() { 41 return "User [id=" + id + ",name=" + name + "]"; 42 } 43 } 44 45 public static class ChangeObjectThread extends Thread { 46 47 @Override 48 public void run() { 49 while (true) { 50 synchronized (user) { 51 if (Thread.currentThread().isInterrupted()) { 52 break; 53 } 54 int i = (int) System.currentTimeMillis() / 1000; 55 user.setId(i); 56 try { 57 Thread.sleep(100); 58 } catch (InterruptedException e) { 59 Thread.currentThread().interrupt(); 60 } 61 user.setName("" + i); 62 63 } 64 Thread.yield(); 65 } 66 } 67 } 68 69 public static class ReadObjectThread extends Thread { 70 71 @Override 72 public void run() { 73 while (true) { 74 synchronized (user) { 75 if (user.getId() != Integer.parseInt(user.getName())) { 76 System.out.println(user); 77 } 78 } 79 Thread.yield(); 80 } 81 } 82 } 83 }
Main方法:
1 package com.bdqn.lyrk.basic; 2 3 public class Main { 4 public static void main(String[] args) throws InterruptedException { 5 new StopThreadUnsafe.ReadObjectThread().start(); 6 while (true) { 7 StopThreadUnsafe.ChangeObjectThread thread = new StopThreadUnsafe.ChangeObjectThread(); 8 thread.start(); 9 Thread.sleep(200); 10 thread.stop(); 11 // thread.interrupt(); 12 } 13 } 14 }
此时调用stop终止线程时,得到如下结果
User [id=1197577,name=1197576] User [id=1197577,name=1197576] User [id=1197577,name=1197576] User [id=1197577,name=1197576] User [id=1197578,name=1197577]
原因很简单:stop方法会释放对象锁,并终止线程,当线程还没有与name赋值时,已经被干掉了因此其他线程在读取时,很有可能读到NAME与ID值不一致的情况
二。守护线程Daemon
守护线程顾名思义,是系统的守护者,这个线程为系统的运行默默提供服务,当系统任务运行完毕,守护线程也就完成使命了,比如说垃圾回收线程,JIT线程都是守护线程,设置守护线程的方式:在调用start方法前,通过线程对象.setDaemon(true)
代码如下:
1 package com.bdqn.lyrk.basic; 2 3 public class DaemonDemo { 4 public static class DaemonT extends Thread { 5 @Override 6 public void run() { 7 while (true){ 8 System.out.println("I am alive"); 9 try { 10 Thread.sleep(1000); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 } 16 } 17 18 public static void main(String[] args) throws InterruptedException { 19 DaemonT daemonT = new DaemonT(); 20 daemonT.setDaemon(true); 21 daemonT.start(); 22 Thread.sleep(5000); 23 } 24 }
运行结果
I am alive I am alive I am alive I am alive I am alive Process finished with exit code 0
我们可以看到,当主线程执行完毕后,守护线程也随之结束
三。wait与notify
wait与notify是多线程协同工作的最基本手段,可是这两个方法属于Object的方法,当需要使用wait和notify时,必须配合synchronized使用,此时调用wait方法,当前线程会进入等待队列并释放当前的对象锁,直到线程被唤醒(notify),notify方法会随机唤醒一个在等待队列中的线程,notifyAll方法则唤醒所有在等待队列中的线程
代码示例:
1 package com.bdqn.lyrk.basic; 2 3 public class SimpleWN { 4 public static final Object object = new Object(); 5 6 public static class T1 extends Thread { 7 8 @Override 9 public void run() { 10 synchronized (object) { 11 System.out.println("开始执行线程..."); 12 try { 13 object.wait(); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 System.out.println("结束执行线程..."); 18 } 19 } 20 } 21 22 public static class T2 extends Thread { 23 @Override 24 public void run() { 25 synchronized (object) { 26 System.out.println("5秒后准备唤醒线程.."); 27 try { 28 Thread.sleep(5000); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 object.notify(); 33 } 34 } 35 } 36 37 public static void main(String[] args) { 38 T1 t1 = new T1(); 39 T2 t2 = new T2(); 40 t1.start(); 41 t2.start(); 42 } 43 }
输出内容:
开始执行线程... 5秒后准备唤醒线程.. 结束执行线程... Process finished with exit code 0
注意以下几点:
1)当线程T1被notify过后,也必须要重新获取对象锁,才能够继续执行
2)sleep也能达到wait的效果,但是唯一区别时,sleep时并不会释放对象锁,因此其他线程并没有得到执行的机会