Java 中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。即“线程中断”并不是字面意思——线程真的中断了,而是设置了中断标志位为true。
1 thread.interrupt()
该方法“中断线程”,但仅仅是会设置该线程的中断状态位为true,至于中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。
线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会立即中断一个正在运行的线程,因此没有stop()方法带来的的问题。
如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、condition.await、以及可中断的通道上的 I/O 操作等方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
synchronized在获锁的过程中的阻塞态是不能被中断的(可以设置标志位为true,但是不会抛出异常,因此不能从阻塞态中返回),与synchronized功能相似的lock.lock()方法也是一样,它也不可中断的,即如果发生死锁,意思是说如果产生了死锁,则不可能中断某个发生死锁而处于等待的线程。但是如果调用带超时的tryLock方法lock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用lock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。
有一个特例是:被LockSupport.park()
阻塞的线程也可以被中断,但是不会抛出异常,并且不会恢复标志位。
2 thread.isInterrupted()
如果要检测当前线程是否被中断,请使用该方法,因为它不会清除中断标示位,即不会将中断标设置为false,就仅仅是检测状态而已。
publicbooleanisInterrupted(){returnisInterrupted(false);}//ClearInterruptedfalse不重置标志位;true重置标志位privatenativebooleanisInterrupted(booleanClearInterrupted);
判断是否中断还有一个方法Thread.interrupted(),但是请谨慎使用。该方法调用后会将中断标示位清除,即重新设置为false。并且该方法是Thread类的静态方法。
publicstaticbooleaninterrupted(){returncurrentThread().isInterrupted(true);}
interrupted()内部是获取当前调用线程的中断标志,而isInterrupted()则是获取调用isInterrupted()方法的实例对象的中断标志。
下面验证:
publicclassinterrupted{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(()->{while(true){}});thread.start();thread.interrupt();//获取中断标志,获取子线程的中断标志System.out.println("isInterrupted:"+thread.isInterrupted());//获取中断标志并重置,虽然是thread.interrupted(),但实际上是获取主线程的中断标志,因为在主线程中调用的System.out.println("interrupted:"+thread.interrupted());//获取中断标志并重置,也是获取主线程的中断标志System.out.println("interrupted:"+Thread.interrupted());//获取中断标志,获取子线程的中断标志System.out.println("interrupted:"+thread.isInterrupted());}}
结果是
isInterrupted:trueinterrupted:falseinterrupted:falseinterrupted:true
3 应用
当线程为了等待一些特定条件的到来时,一般会调用sleep函数、wait 系列函数或者join ()函数来阻塞挂起当前线程。
比如一个线程调用了Thread.sleep(3000),那么调用线程会被阻塞,直到3s 后才会从阻塞状态变为激活状态。但是有可能在3s 内条件己被满足,如果一直等到3s后再返回有点浪费时间,这时候可以调用该线程的interrupt()方法, 强制sleep 方法抛出InterruptedException 异常而返回,线程恢复到激活状态。
publicclassInterruptTest{publicstaticvoidmain(String[]args)throwsInterruptedException{Interruptinterrupt=newInterrupt();Threadthread1=newThread(interrupt,"thread1");Threadthread2=newThread(interrupt,"thread2");thread1.start();//主线程睡眠一秒,等待thread1获得锁,再开启thread2Thread.currentThread().sleep(1000);thread2.start();//主线程再次睡眠一秒,等待thread2由于获取不到锁而处于BLOCKED阻塞态(阻塞在synchronized处)Thread.currentThread().sleep(1000);//获取thread2状态System.out.println(thread2.getName()+"状态:"+thread2.getState());//尝试设置thread2的中断状态thread2.interrupt();//获取是否thread2是否处于中断,返回true,说明是,//但是没有抛出异常,此时我们便没办法处理这个出于阻塞态的线程System.out.println(thread2.getName()+"是否是中断状态:"+thread2.isInterrupted());System.out.println(thread2.getName()+"由于处于synchronized阻塞态,即使处于中断态,也没有抛出异常,无法结束");System.out.println();System.out.println("=========>开始处理这个问题");System.out.println();//获取thread1的状态,可以发现是TIMED_WAITING.并且是采用Thread.sleep(100)方式,可以被中断并抛出异常System.out.println(thread1.getName()+"状态:"+thread1.getState());//尝试设置thread1的中断状态thread1.interrupt();System.out.println(thread1.getName()+"是否是中断状态:"+thread1.isInterrupted());//由于thread1中断了等待,处理了异常,结束了运行,释放了锁。//thred2获得了锁,进入同步块,执行sleep方法进入TIMED_WAITING状态//由于thread2在前面设置了中断状态,因此也立即从sleep方法出抛出异常,并且正常结束线程。}staticclassInterruptimplementsRunnable{@Overridepublicvoidrun(){synchronized(Thread.class){try{//处于等待该等待方法可被中断,此时会中断等待,并且抛出异常,程序可以结束.Thread.sleep(100000);}catch(InterruptedExceptione){//cache处理中断异常,结束等待,使得线程能正常结束运行System.out.println(Thread.currentThread().getName()+"抛出了异常,结束了TIMED_WAITING,该线程可以返回");}}}}}
4 停止线程
这里的停止线程就是指结束线程。常见方法有以下几种
正常运行结束 程序运行结束,线程自动结束。
stop方法:但是已经过时,容易引发死锁。
即刻停止run()方法中剩余的全部工作,包括在catch或finally语句中(如果不在try块中)并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
会立即释放该线程所持有的所有的锁,导致数据得不到同步的处理,出现数据不一致的问题。如果以前受这些监视器保护的任何对象都处于不连贯状态,那么损坏的对象对其他线程可见,这有可能导致不安全的操作。
通过修改某个变量来终止线程。
即在线程当中,定义一个标志位,通过改变标志位的形式,结束线程。
thread.interrupt()
使用interrupt方法可以中断线程的等待状态,并不是直接把线程终止。我们可以配合抛出的异常来停止部分等待状态的线程
如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int)方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException——当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常。我们可以在catch当中捕获到异常,并且改变标志位,或者跳出循环,即可结束线程。
如果想要中断等待获得锁的线程的等待状态,那么该方法需要配合lock.lockInterruptibly()或者trylock()获取锁的方法!因为这种获取锁的方式允许等待锁的线程被中断。如果使用lock()方法,那么interrupt()方法无效,无法中断因为获取不到锁而等待(受阻)的线程,synchronized等待也是无法中断的。
如果有什么不懂或者需要交流,各位可以留言。另外,希望收藏、关注一下,我将不间断更新Java各种教程!