一般来说,线程在执行完毕后就会结束,无须手动关闭。但是,凡是也有例外,一些服务端的后台程序可能会常驻系统,它们通常不会正常终结。比如,他们的执行体本身就是一个大的无穷循环。
先来个总结:一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
在Thread中有一个stop()方法,如果使用stop()方法可以立即将一个线程中止,但是这个方法已经被废弃。因为这个方法是在是太过暴力,会强行将执行一般的线程直接终止,并且会立即释放这个线程所持有的锁,这样就会引发很多问题。
下面简单实现一个安全的停止线程的方式:
//......
public class StopThreadTest extends Thread {
volatile boolean stopMe = false;
public void stopMe(){
stopMe = true;
}
@Override
public void run() {
while (true){
if (stopMe){
System.out.println("线程【"+this.getName()+"】stop......");
break;
}
//do sth
System.out.println("正在执行!!!");
Thread.yield();
}
}
}
//......
suspend()(挂起)和resume()(继续执行)是一对相反的操作,被挂起的线程必须要等到resume()后才能够继续执行。看着好像很好用,但是这两个方法也已经被废弃了。不推荐使用suspend()的原因是因为suspend()在导致线程暂停的同时,并不会释放任何资源,这样就会有个问题,如果当前线程A持有一把锁,当A被挂起之后,其他任何线程想要访问被线程A占用的锁时,都会被牵连,导致其他线程无法正常地运行,除非线程A执行了resume()方法,而一般线程A的resume()方法肯定是由另一个线程执行的,而线程的很多操作是难以控制的,如果resume()方法意外地在suspend()方法前面执行了,那么线程A就难以继续执行,而它持有的锁也不会释放这样就会引发死锁问题。
而且被挂起的线程是处于RUNNABLE(具体可以参看:并发编程学习之线程的生命周期)状态,这样会验证影响我们对系统当前状态的判断。
以下代码示例简单演示了使用suspend()会出现的问题:
package dgb.test.concurrent;
/**
* @author Dongguabai
* @date 2018/9/2 19:35
*/
public class SuspendTest {
public static void main(String[] args) throws InterruptedException {
ChangeObjectThread c1 = new ChangeObjectThread("thread-1");
ChangeObjectThread c2 = new ChangeObjectThread("thread-2");
c1.start();
c2.start();
// Thread.sleep(1000);
c1.resume();
c2.resume();
}
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("线程【" + getName() + "】正在执行");
Thread.currentThread().suspend();
}
}
}
测试结果:
由于resume()意外的在suspend()前面执行了。两个线程不会退出,而是会挂起。可以查看线程信息看看(具体查看方法可以参看:jstack使用):
这时候发现线程的状态是RUNNABLE的,但是实际上它是被挂起的,这样会使我们误判当前线程的状态。
这里有一个示例使用wait()和notify()实现suspend()和resume(),思路和上面是一样的:
package dgb.test.concurrent;
import java.time.LocalDateTime;
/**
* @author Dongguabai
* @date 2018/9/2 19:35
*/
public class SuspendTest2 {
private static final Object u = new Object();
public static void main(String[] args) throws InterruptedException {
ChangeObjectThread c1 = new ChangeObjectThread("thread-1");
ReadThread r = new ReadThread();
c1.start();
r.start();
Thread.sleep(1000);
c1.mySuspend();
Thread.sleep(60000);
c1.myResume();
}
public static class ChangeObjectThread extends Thread {
private volatile boolean mySuspend = false;
public ChangeObjectThread(String name) {
super(name);
}
public void mySuspend() {
// System.out.println("线程【" + getName() + "】mySuspend==");
mySuspend = true;
}
public void myResume() {
//System.out.println("线程【" + getName() + "】myResume==");
mySuspend = false;
synchronized (this) {
this.notify();
//System.out.println("线程【" + getName() + "】执行notify==");
}
}
@Override
public void run() {
while (true) {
synchronized (this) {
while (mySuspend) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized (u) {
System.out.println("线程【" + getName() + "】正在执行,当前时间:" + LocalDateTime.now());
}
Thread.yield();
}
}
}
public static class ReadThread extends Thread {
@Override
public void run() {
while (true) {
synchronized (u) {
System.out.println("ReadThread执行。。。。");
}
Thread.yield();
}
}
}
}
线程的中断时一种重要的线程协作机制,线程中断并不会使线程立即退出,而是给线程发送一个通知,告诉线程,有人想你退出了,至于目标线程后续会如何处理,则是由目标线程自己决定,是与stop()不同的。
关于线程中断有三个方法:
public void Thread.interrupt() //中断线程
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
interrupt
public void interrupt()中断线程。
如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的
checkAccess
方法就会被调用,这可能抛出SecurityException
。如果线程在调用
Object
类的wait()
、wait(long)
或wait(long, int)
方法,或者该类的join()
、join(long)
、join(long, int)
、sleep(long)
或sleep(long, int)
方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException
。如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个
ClosedByInterruptException
。如果该线程在一个
Selector
中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的wakeup
方法一样。如果以前的条件都没有保存,则该线程的中断状态将被设置。
中断一个不处于活动状态的线程不需要任何作用。
抛出:
SecurityException
- 如果当前线程无法修改该线程
interrupted
public static boolean interrupted()测试当前线程是否已经中断。线程的 中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回 false 的方法反映出来。
返回:
如果当前线程已经中断,则返回
true
;否则返回false
。另请参见:
isInterrupted()
isInterrupted
public boolean isInterrupted()测试线程是否已经中断。线程的 中断状态 不受该方法的影响。
线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回 false 的方法反映出来。
返回:
如果该线程已经中断,则返回
true
;否则返回false
。另请参见:
interrupted()
Thread.interrupt()方法是一个实例方法。它通知目标线程中断,也就是设置中断标志位。中断标志位表示当前线程已经被中断了。Thread.isInterrupted() 也是一个实例方法,它判断当前线程是否有被中断(检查标志位)。最后的静态方法Thread.interrupted()也是用来判断当前线程的字段状态,但同时会清除当前线程中断标志位状态。
在下面这个示例中,虽然设置了中断标识位,但是线程中并没有中断处理的逻辑,因此,即使线程被设置了中断标志位,但是这个中断不会起任何作用:
如果想让线程在中断后退出,就要增加相应的中断处理代码,这样就不会武断的将线程中止,这样的操作更加安全和优雅:
package dgb.test.concurrent;
/**
* @author Dongguabai
* @date 2018/9/2 23:13
*/
public class InterruptedTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
if(Thread.currentThread().isInterrupted()){
System.out.println("线程已被中断-------");
break;
}
System.out.println("正在执行------------");
}
}
);
thread.start();
Thread.sleep(1000);
System.out.println("中断。。。。。");
thread.interrupt();
}
}
在Java的API中,很多声明都会抛出InterruptedException异常,这些方法在抛出InterruptedException之前。JVM会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。
InterruptedException不是运行时异常,也就是说程序必须捕获并且处理它。
Thread.sleep()方法会让当前线程休眠若干时间,它会抛出InterruptedException异常。InterruptedException不是运行时异常,也就是说程序必须捕获并且处理它,当线程在sleep()休眠时,如果被中断,这个异常就会产生。
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
*
* @param millis
* the length of time to sleep in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* interrupted status of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
比如修改一下上面那段代码:
如果在第20行处,线程被中断了,那么程序会进入23行,此时中断标识位已被清除,在这里又执行了一次Thread.interrupt()方法中断自己,只有这样,当再次执行的时候会发现线程已经中断了,从而能够保证数据的一致性和完整性。