Java并发08:Thread的基本方法(5)-interrupt()、isInterrupted()
本章主要对Java中Thread类的基本方法进行学习。
1.序言
Thread类作为线程的基类,提供了一系列方法,主要有:
Thread.sleep(long):强制线程睡眠一段时间。
Thread.activeCount():获取当前程序中存活的线程数。
thread.start():启动一个线程。
Thread.currentThread():获取当前正在运行的线程。
thread.getThreadGroup():获取线程所在线程组。
thread.getName():获取线程的名字。
thread.getPriority():获取线程的优先级。
thread.setName(name):设置线程的名字。
thread.setPriority(priority):设置线程的优先级。
thread.isAlive():判断线程是否还存活着。
thread.isDaemon():判断线程是否是守护线程。
thread.setDaemon(true):将指定线程设置为守护线程。
thread.join():在当前线程中加入指定线程,使得这个指定线程等待当前线程,并在当前线程结束前结束。
thread.yield():使得当前线程退让出CPU资源,把CPU调度机会分配给同样线程优先级的线程。
thread.interrupt():使得指定线程中断阻塞状态,并将阻塞标志位置为true。
object.wai()、object.notify()、object.notifyAll():Object类提供的线程等待和线程唤醒方法。
为了便于阅读,将以上所有方法,放在5篇文章中进行学习。
本章主要学习绿色字体标记的方法,其他方法请参加其他章节。
2.interrupt()
/** * Interrupts this thread. * * <p> Unless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. * * <p> If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. * * <p> If this thread is blocked in an I/O operation upon an {@link * java.nio.channels.InterruptibleChannel InterruptibleChannel} * then the channel will be closed, the thread's interrupt * status will be set, and the thread will receive a {@link * java.nio.channels.ClosedByInterruptException}. * * <p> If this thread is blocked in a {@link java.nio.channels.Selector} * then the thread's interrupt status will be set and it will return * immediately from the selection operation, possibly with a non-zero * value, just as if the selector's {@link * java.nio.channels.Selector#wakeup wakeup} method were invoked. * * <p> If none of the previous conditions hold then this thread's interrupt * status will be set. </p> * ... */ public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); }
说明:
- 除非是线程自己interrupt()自己,否则checkAccess()方法都会被调用,并可能抛出一个SecurityException异常。
- 如果当前线程处于blocked阻塞(因为调用wait、sleep和join造成的)状态时被interrupt了,那么[中断标志位]将被清除,并且收到一个InterruptedException异常。
- 如果当前线程处于blocked阻塞(因为NIO的InterruptibleChannel进行的I/O操作造成的)状态时被interrupt了,则会关闭channel,[中断标志位]将会被置为true,并且当前线程会收到一个ClosedByInterruptException异常。
- 如果当前线程处于blocked阻塞(因为NIO的Selector造成的)状态时被interrupt了,那么[中断标志位]将被置为true,然后当前线程会立即从选择器区域返回并返回值(可能为非零的值)。
如果前面的情况都没有发生,则线程会将[中断标志位]将被置为true。
更加易懂的说法(不包括NIO部分):
- interrupt()方法并不是中断线程,而是中断阻塞状态,或者将线程的[中断标志位]置为true。
- 对于未阻塞的线程,interrupt()只是造成[中断标志位]=rue,线程本身运行状态不受影响。
- 对于阻塞的线程,interrupt()会中断阻塞状态,使其转换成非阻塞状态,并清除[中断标志位]。
- 造成阻塞状态的情况有:sleep()、wait()和join()。
- 阻塞状态的线程被中断时,只是中断了阻塞状态,即sleep()、wait()和join(),线程本身还在继续运行。
3.实例代码与结果
为了更加深刻的学习interrupt()方法,下面通过5个例子来进行说明。
3.1.非阻塞线程的interrupt()
下面的代码,展示了非阻塞线程的interrupt():
//interrupt()并不是中断线程 //interrupt():中断线程的阻塞(sleep/join/wait)状态,或将 中断标志位 置为 true //所以如果是普通的运行中的线程,流程并不会受到影响 LOGGER.info("===========interrupt()并不是中断线程,只是将线程的 中断标志位 置为true"); new Thread(() -> { Thread thread = Thread.currentThread(); for (int i = 0; i < 5; i++) { LOGGER.info("线程[" + thread.getName() + "]: i = " + i + ",isInterrupted = " + thread.isInterrupted()); if (i == 3) { thread.interrupt(); } } LOGGER.info("线程[" + Thread.currentThread().getName() + "]: 停止运行" + ",isInterrupted = " + thread.isInterrupted()); }).start();
运行结果:
2020-03-12 15:10:42 INFO ThreadInterruptDemo:23 - ===========interrupt()并不是中断线程,只是将线程的 中断标志位 置为true 2020-03-12 15:10:42 INFO ThreadInterruptDemo:27 - 线程[Thread-0]: i = 0,isInterrupted = false 2020-03-12 15:10:42 INFO ThreadInterruptDemo:27 - 线程[Thread-0]: i = 1,isInterrupted = false 2020-03-12 15:10:42 INFO ThreadInterruptDemo:27 - 线程[Thread-0]: i = 2,isInterrupted = false 2020-03-12 15:10:42 INFO ThreadInterruptDemo:27 - 线程[Thread-0]: i = 3,isInterrupted = false 2020-03-12 15:10:42 INFO ThreadInterruptDemo:27 - 线程[Thread-0]: i = 4,isInterrupted = true 2020-03-12 15:10:42 INFO ThreadInterruptDemo:32 - 线程[Thread-0]: 停止运行,isInterrupted =
分析:在i=3的时候进行了线程中断。所以i=4时,isInterrupted = true,但是程序还在继续运行。
结论:对于未阻塞的线程,interrupt()只是造成[中断标志位]=true,线程本身运行状态不受影响。
3.2.通过interrupt()与isInterrupted()控制for循环的运行状态(非阻塞线程)
下面的例子展示了如何通过interrupt()与isInterrupted()实现线程中for循环的中途跳出。
//interrupt()、isInterrupted()可以结合,控制线程中的for(无阻塞状态)循环 Thread.sleep(200); System.out.println(); LOGGER.info("===========interrupt()、isInterrupted()可以结合,控制线程中的for(无阻塞状态)循环"); new Thread(() -> { Thread thread = Thread.currentThread(); for (int i = 0; i < 5 && !thread.isInterrupted(); i++) { LOGGER.info("线程[" + thread.getName() + "] is running, i = " + i + ",isInterrupted = " + thread.isInterrupted()); if (i == 3) { thread.interrupt(); LOGGER.info("线程[" + thread.getName() + "] is isInterrupted, i = " + i + ", isInterrupted = " + thread.isInterrupted()); } } LOGGER.info("线程[" + Thread.currentThread().getName() + "] 停止运行"); }).start();
运行结果:
2020-03-12 15:10:42 INFO ThreadInterruptDemo:38 - ===========interrupt()、isInterrupted()可以结合,控制线程中的for(无阻塞状态)循环 2020-03-12 15:10:42 INFO ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 0,isInterrupted = false 2020-03-12 15:10:42 INFO ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 1,isInterrupted = false 2020-03-12 15:10:42 INFO ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 2,isInterrupted = false 2020-03-12 15:10:42 INFO ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 3,isInterrupted = false 2020-03-12 15:10:42 INFO ThreadInterruptDemo:45 - 线程[Thread-1] is isInterrupted, i = 3, isInterrupted = true 2020-03-12 15:10:42 INFO ThreadInterruptDemo:48 - 线程[Thread-1] 停止运行
分析:其实就是利用了isInterrupted()的true和false状态。
备注:前提条件是当前线程时非阻塞的。
3.3.通过interrupt()与isInterrupted()控制while循环的运行状态(非阻塞线程)
下面的代码展示了如何通过interrupt()与isInterrupted()控制非阻塞线程中的无限while循环。
//interrupt()、isInterrupted()可以结合,控制线程中的while(无阻塞状态)循环 Thread.sleep(200); System.out.println(); LOGGER.info("在无阻塞状态(sleep/wait/joni)的while循环中应用interrupt()和isInterrupted()"); Thread thread1 = new Thread(() -> { //如果当前线程没被中断,则一直进行 while (!Thread.currentThread().isInterrupted()) { LOGGER.info("线程[" + Thread.currentThread().getName() + "]正在运行..."); } LOGGER.info("线程[" + Thread.currentThread().getName() + "]停止运行"); }); thread1.start(); Thread.sleep(10); thread1.interrupt();
运行结果:
2018-03-12 15:10:42 INFO ThreadInterruptDemo:54 - 在无阻塞状态(sleep/wait/joni)的while循环中应用interrupt()和isInterrupted() 2018-03-12 15:10:42 INFO ThreadInterruptDemo:58 - 线程[Thread-2]正在运行... 2018-03-12 15:10:42 INFO ThreadInterruptDemo:58 - 线程[Thread-2]正在运行... 2018-03-12 15:10:42 INFO ThreadInterruptDemo:58 - 线程[Thread-2]正在运行... 2018-03-12 15:10:42 INFO ThreadInterruptDemo:58 - 线程[Thread-2]正在运行... 2018-03-12 15:10:42 INFO ThreadInterruptDemo:58 - 线程[Thread-2]正在运行... ... 2018-03-12 15:10:42 INFO ThreadInterruptDemo:60 - 线程[Thread-2]停止运行
分析:
在有阻塞的线程中,调用interrupt方法,会中断当前的阻塞状态。如本例子中,线程本来应该睡眠5秒钟打印一次信息,但是因为interrupt方法直接中断了本次睡眠,然后进入到下次循环中。
在有阻塞的线程中,调用interrupt方法,会抛出InterruptedException异常。
在有阻塞的线程中,调用interrupt方法,会清空[中断标志位],导致程序一直运行下去。
3.5.阻塞线程(sleep/wait/joni)中的while循环应用interrupt()和isInterrupted()的正确姿势
把上面的代码修改一下,将try-catch提取至最外层,并将错误信息打印屏蔽,形成如下代码:
//中断有阻塞状态(sleep/wait/joni)的线程,会产生一个InterruptedException异常,并将中断标志位清空,所以不会结束线程 //所以在有阻塞状态(sleep/wait/joni)的while循环,除了应用interrupt()和isInterrupted()的结合外,还需要在外层catch这个异常,才能够停止线程 Thread.sleep(200); System.out.println(); LOGGER.info("在有阻塞状态(sleep/wait/joni)的while循环中应用interrupt()和isInterrupted()+catch"); Thread thread3 = new Thread(() -> { try { //如果当前线程没被中断,则一直进行 while (!Thread.currentThread().isInterrupted()) { LOGGER.info("线程[" + Thread.currentThread().getName() + "]正在运行..."); Thread.sleep(5000); } } catch (InterruptedException e) { LOGGER.info("线程[" + Thread.currentThread().getName() + "]停止运行"); //没有必要打印错误,因为这是故意让程序产生的错误 //e.printStackTrace(); } }); thread3.start(); Thread.sleep(2000); thread3.interrupt();
运行结果:
2018-03-12 15:52:40 INFO ThreadInterruptDemo:91 - 在有阻塞状态(sleep/wait/joni)的while循环中应用interrupt()和isInterrupted()+catch 2018-03-12 15:52:40 INFO ThreadInterruptDemo:96 - 线程[Thread-0]正在运行... 2018-03-12 15:52:42 INFO ThreadInterruptDemo:100 - 线程[Thread-0]停止运行
使用interrupt()中断阻塞线程的正确方法:
//在线程定义类中 @Override public void run(){ try{ //阻塞代码:sleep、join和wait }catch(InterruptedException e){ //注释掉e.printStackTrace(); //添加其他业务代码 } } //在线程使用时 thread.interrupt();