本章主要对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();
运行结果:
2018-03-12 15:10:42 INFO ThreadInterruptDemo:23 - ===========interrupt()并不是中断线程,只是将线程的 中断标志位 置为true
2018-03-12 15:10:42 INFO ThreadInterruptDemo:27 - 线程[Thread-0]: i = 0,isInterrupted = false
2018-03-12 15:10:42 INFO ThreadInterruptDemo:27 - 线程[Thread-0]: i = 1,isInterrupted = false
2018-03-12 15:10:42 INFO ThreadInterruptDemo:27 - 线程[Thread-0]: i = 2,isInterrupted = false
2018-03-12 15:10:42 INFO ThreadInterruptDemo:27 - 线程[Thread-0]: i = 3,isInterrupted = false
2018-03-12 15:10:42 INFO ThreadInterruptDemo:27 - 线程[Thread-0]: i = 4,isInterrupted = true
2018-03-12 15:10:42 INFO ThreadInterruptDemo:32 - 线程[Thread-0]: 停止运行,isInterrupted = true
分析:在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();
运行结果:
2018-03-12 15:10:42 INFO ThreadInterruptDemo:38 - ===========interrupt()、isInterrupted()可以结合,控制线程中的for(无阻塞状态)循环
2018-03-12 15:10:42 INFO ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 0,isInterrupted = false
2018-03-12 15:10:42 INFO ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 1,isInterrupted = false
2018-03-12 15:10:42 INFO ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 2,isInterrupted = false
2018-03-12 15:10:42 INFO ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 3,isInterrupted = false
2018-03-12 15:10:42 INFO ThreadInterruptDemo:45 - 线程[Thread-1] is isInterrupted, i = 3, isInterrupted = true
2018-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]停止运行
分析:其实就是利用了isInterrupted()的true和false状态。
备注:前提条件是当前线程时非阻塞的。
3.4.阻塞线程(sleep/wait/joni)中的while循环应用interrupt()和isInterrupted()
下面展示了在线程阻塞(sleep/wait/joni)的情况下,应用interrupt()和isInterrupted()的结果:
//中断有阻塞状态(sleep/wait/joni)的线程,会产生一个InterruptedException异常,并将中断标志位清除,所以不会结束线程
//所以在有阻塞状态(sleep/wait/joni)的while循环,只是应用interrupt()和isInterrupted()的结合,虽然会报错,但并不能停止线程
Thread.sleep(200);
System.out.println();
LOGGER.info("在有阻塞状态(sleep/wait/joni)的while循环中应用interrupt()和isInterrupted()");
Thread thread2 = new Thread(() -> {
//如果当前线程没被中断,则一直进行
while (!Thread.currentThread().isInterrupted()) {
LOGGER.info("线程[" + Thread.currentThread().getName() + "]正在运行...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
LOGGER.info("线程[" + Thread.currentThread().getName() + "]停止运行");
});
thread2.start();
Thread.sleep(20);
thread2.interrupt();
运行结果:
2018-03-12 15:47:43 INFO ThreadInterruptDemo:70 - 在有阻塞状态(sleep/wait/joni)的while循环中应用interrupt()和isInterrupted()
2018-03-12 15:47:43 INFO ThreadInterruptDemo:74 - 线程[Thread-0]正在运行...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at pers.hanchao.concurrent.eg04.ThreadInterruptDemo.lambda$main$0(ThreadInterruptDemo.java:76)
at java.lang.Thread.run(Thread.java:748)
2018-03-12 15:47:43 INFO ThreadInterruptDemo:74 - 线程[Thread-0]正在运行...
2018-03-12 15:47:48 INFO ThreadInterruptDemo:74 - 线程[Thread-0]正在运行...
2018-03-12 15:47:53 INFO ThreadInterruptDemo:74 - 线程[Thread-0]正在运行...
...无限
分析:
- 在有阻塞的线程中,调用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();