结束线程的思考
早前java中线程的结束有2种:
1.线程的run()方法执行完毕时,该线程会结束。
2.是线程调用stop方法结束。
后来java认为在线程中去强制结束其他线程是不安全的,因为被结束的线程可能会有某些必要的操作还来不及执行。当然这个解释只是从网上抄过来的,api中说明的Deprecated原因是说stop将导致它解锁所有已锁定的监视器(这是未检查的ThreadDeath异常向上传播堆栈的自然结果),其实每个对象包含了对象头,实例数据和填充数据,对象头中保存了锁的标志位和指向monitor对象的起始地址。这个monitor中包含一个叫Owner的部分,当这个Owner指向某一个线程,就说明该对象被这个线程锁定。当然这么底层的东西我是看《深入Java虚拟机》这本书知道的,再深入的东西我也不知道了,并且这不是java层面的东西,我小菜鸡不懂。
好了,思考完了,咱们来看下java现在的推荐。 java推荐使用中断机制来让被调用的线程自己来判断自己要不要死。如果它还没活够那就继续,如果想死了那就自己走吧。
Interrupt相关API
public void interrupt(){...}
public boolean isInterrupted() {
return isInterrupted(false);
}
//该方法是静态方法,一般用于需要被interrupt的线程内部来判断本线程是否被其他线程执行了interrupt()
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
这三个api只有interrupted是Thread的静态方法,其他俩都是非静态方法。
interrupt():修改中断标志位,并将调用该方法时程序所处的线程退出阻塞状态,此时会通知到被标记的线程并且调用阻塞处会接受到InterruptedException异常。这里的阻塞比如是调用了sleep,wait,join方法,线程在等待同步锁的过程不属于本阻塞范畴。
isInterrupted():判断调用的线程标志位是否为中断,是返回true,否则返回false。
interrupted:判断当前线程是否为中断标记,如果是则返回true,否则返回false,并且它在每调用一次之后都会清除该标志位,也就是设置为false。
interrupt()的使用一般有2种情况:
1.被interrupt的线程内部没有有阻塞方法
2.被interrupt的线程内部有阻塞方法(例如wait,sleep,join)
当一个线程中没有阻塞,我们可能有时候也需要知道其他线程是否调用了interrupt()想让本线程中断,但是这时候没有阻塞的方法可以接收InterruptedException异常,那我们可以换个思路:通过Thread.interrupted()方法来获取当前本线程的中断标志位的值,并且为了在被调用了interrupt()方法时能更实时的接收到这一信息,往往是开启一个循环来判断,这也是一种常用的使用方法。例如下列代码:
public void main() throws InterruptedException {
//不阻塞的情况
Thread thread1 = new Thread(() -> {
System.out.println("执行" + Thread.currentThread().getName() + "线程");
while (true) {
if (Thread.interrupted()) {
//假设如果是被执行了中断,则我们选择退出本线程,当然这里的条件判断也可以加上其他的与业务有关的代码,自行理会了
System.out.println("某个线程调用了本线程对象的interrupt()方法,我们响应这个中断");
return;
} else {
//假设未被执行中断,那我们就一直做某些操作
System.out.println(Thread.currentThread().getName() + " do someting ...");
}
}
}, "thread1");
Thread thread2 = new Thread(() -> {
System.out.println("执行" + Thread.currentThread().getName() + "线程");
try {
//先睡2秒再接着往下走
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//提示中断thread1线程中的阻塞,但是执行本行代码的时候,并不会真正中断
thread1.interrupt();
}, "thread2");
}
输出结果为:
thread1 do someting ...
thread1 do someting ...
thread1 do someting ...
thread1 do someting ...
thread1 do someting ...
thread1 do someting ...
thread1 do someting ...
thread1 do someting ...
thread1 do someting ...
某个线程调用了本线程对象的interrupt()方法,我们响应这个中断
当一个线程的interrupt()方法被调用时,该线程中如果有阻塞方法正在执行,则会收到InterruptedException异常,接收到该异常之后线程可以自己根据业务逻辑决定是否结束本线程。需要注意的是,接收到该异常之后,线程并不会直接关闭,而是将该线程退出阻塞状态,然后继续执行以下代码,并且这时中断标志位会被清除,设置为false。
没有去研究底层的native实现,所以咱们只能做一些测试来验证这些解释。
首先我们写个例子来看看interrupt是怎么用的:
public void main() throws InterruptedException {
Thread thread1 = new Thread(() -> {
System.out.println("执行" + Thread.currentThread().getName() + "线程");
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
//当本线程的interrupt()方法被调用的时候,此处的sleep为阻塞方法,这里会接收到该异常,这时线程可以自行决定是否结束本线程.(原则上其实本线程也可以调用自身的interrupt()方法的)
System.out.println("接收到" + e + "异常,程序继续向下执行");
}
System.out.println("线程继续执行,此时本线程的中断状态为:" + Thread.interrupted());
}, "thread1");
Thread thread2 = new Thread(() -> {
System.out.println("执行" + Thread.currentThread().getName() + "线程");
try {
//先睡2秒再接着往下走
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//提示中断thread1线程中的阻塞,但是执行本行代码的时候,并不会真正中断
thread1.interrupt();
}, "thread2");
thread1.start();
thread2.start();
}
输出结果:
执行thread2线程
执行thread1线程
接收到java.lang.InterruptedException: sleep interrupted异常,程序继续向下执行
线程继续执行,此时本线程的中断状态为:false
我们可以看到,在上面例子中,当thread2调用了thread1.interrupt()方法时,thread1中的阻塞代码接收到了异常,并且因为我们接收到异常之后没有管他,所以程序还是可以继续往下执行,并且此时中断状态为false,验证了我们对interrupt()方法的解释。
我们再来改造一下代码,把thread1的阻塞方式换成wait()我们来看下是不是也是这个效果:
public void main() throws InterruptedException {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
System.out.println("执行" + Thread.currentThread().getName() + "线程");
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
//当本线程的interrupt()方法被调用的时候这里会接收到该异常,这时线程可以自行决定是否结束本线程.(原则上其实本线程也可以调用自身的interrupt()方法的)
System.out.println("接收到" + e + "异常,程序继续向下执行");
}
}
System.out.println("线程继续执行,此时本线程的中断状态为:" + Thread.interrupted());
}, "thread1");
Thread thread2 = new Thread(() -> {
System.out.println("执行" + Thread.currentThread().getName() + "线程");
try {
//先睡2秒再接着往下走
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//提示中断thread1线程中的阻塞,但是执行本行代码的时候,并不会真正中断
thread1.interrupt();
}, "thread2");
thread1.start();
thread2.start();
此时我们把阻塞代码换成了wait,我们来看执行结果:
执行thread2线程
执行thread1线程
接收到java.lang.InterruptedException异常,程序继续向下执行
线程继续执行,此时本线程的中断状态为:false
结果与sleep时完全一样,wait方法必须在同步代码块中调用,并且必须调用该同步代码块的锁对象的wait。这里的同步啊,锁啊什么的,跟我们本篇讲的interrupt中断没有半毛钱关系,我们中断线程要做的仅仅是通知某个线程说你该中断结束了,然后让线程自己接收到异常之后判断是否结束。不管阻塞方法是什么,是sleep还是wait,不管是释放锁的阻塞还是不释放锁的阻塞,不interrupt()的时候是什么样interrupt()之后还是什么样,没有半点关系。这里说明一下以免有的读者正在学习线程相关东西的时候会浮想联翩,和我一样哈哈哈。
既然都已经sleep和wait都演示过了,不妨就把join方法也一起写一下得了:
public void main() throws InterruptedException {
//join时的interrupt
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "值:" + i);
try {
//这个sleep与其他无关,目的仅仅是为了让thread3走得慢点
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程继续执行,此时本线程的中断状态为:" + Thread.interrupted());
}, "thread1");
Thread3 thread3 = new Thread3(thread1, "thread3");
Thread thread2 = new Thread(() -> {
System.out.println("执行" + Thread.currentThread().getName() + "线程");
try {
//先睡2秒再接着往下走
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//提示中断thread1线程中的阻塞,但是执行本行代码的时候,并不会真正中断
thread3.interrupt();
}, "thread2");
thread1.start();
thread2.start();
thread3.start();
}
class Thread3 extends Thread {
Thread thread;
public Thread3(Thread thread, String name) {
super(name);
this.thread = thread;
}
@Override
public void run() {
System.out.println("执行" + Thread.currentThread().getName() + "线程");
try {
thread.join();
} catch (InterruptedException e) {
//当本线程的interrupt()方法被调用的时候这里会接收到该异常,这时线程可以自行决定是否结束本线程.(原则上其实本线程也可以调用自身的interrupt()方法的)
System.out.println(Thread.currentThread().getName() + "接收到" + e + "异常,程序继续向下执行");
}
System.out.println(Thread.currentThread().getName() + "继续执行,此时本线程的中断状态为:" + Thread.interrupted());
}
}
程序输出结果:
执行thread2线程
执行thread3线程
thread1值:0
thread1值:1
thread1值:2
thread1值:3
thread3接收到java.lang.InterruptedException异常,程序继续向下执行
thread3继续执行,此时本线程的中断状态为:false
thread1值:4
thread1值:5
thread1值:6
thread1值:7
thread1值:8
thread1值:9
线程继续执行,此时本线程的中断状态为:false
本例子中,
thread1是个耗时线程,为了模拟当join被调用时,得等到被调用的线程执行完才能执行调用thread1.join的那个线程(也就是thread3)而写的。
thread3的作用是当这个线程开始执行时,调用thread1.join(),为了实现当thread1执行完之后,执行thread3而设计的。
thread2执行的是中断thread3的阻塞而设计的。
我们可以看到,当3个线程都启动起来的时候,在thread2中的thread3.interrupt()方法未工作之前,thread3必须等到thread1执行完毕才能继续执行到结束。而我们现在等了2s之后调用了thread3.interrupt(),那么这时候thread3中的thread1这个阻塞收到中断通知,这时候thread3会退出中断状态,并且标志位会被设为false,那么就不用等到thread1执行完毕就能继续往下执行thread3的代码了。
同样的,与上面说的一样,interrupt只涉及通知对应的线程应该执行中断了,具体是否中断等逻辑全靠该线程自行控制,同步,锁等情况在调用interrupt与未调用interrupt一样,调用前是什么样调用后还是什么样。
希望本文章能对Thread中断机制还不熟悉的朋友有点参考意义。
|