1. 线程在什么情况下会停止?
在三种情况下会导致线程停止:
- run方法正常执行完毕
- 执行过程中出现异常
- 其它线程干预当前线程,如interrupt、volatile修饰的标识符、已经被弃用的stop、suspend等方法
2. 停止线程的正确方法:interrupt
使用interrupt来通知需要被停止的线程以及该线程如何配合,而不是强制停止,因为被停止的线程对本身的业务逻辑是最熟悉的,而发出停止信号的线程对其业务逻辑是不了解的,我们只能告诉被停止的线程该中断了,而被中断的线程本身拥有最终决定权,这样可以保障数据安全。
- 在非阻塞状态下停止线程
public class MyThreadNormal implements Runnable {
@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted()) {
System.out.println(count++);
}
System.out.println("线程被停止");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThreadNormal());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
.......
308827
308828
308829
线程被停止
- 在阻塞状态下停止线程
public class MyThreadSleep implements Runnable {
@Override
public void run() {
System.out.println("开始阻塞");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束阻塞");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThreadSleep());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
开始阻塞
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at core.stop.right.MyThreadSleep.run(MyThreadSleep.java:8)
at java.lang.Thread.run(Thread.java:748)
结束阻塞
public class MyThreadWait implements Runnable {
private Object obj = new Object();
@Override
public void run() {
System.out.println("开始阻塞");
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("结束阻塞");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThreadWait());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
开始阻塞
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at core.stop.right.MyThreadWait.run(MyThreadWait.java:10)
at java.lang.Thread.run(Thread.java:748)
结束阻塞
wait方法和sleep方法都会导致阻塞状态,在阻塞状态下如果收到中断信号,便会响应中断,抛出异常。所以,如果代码中存在wait或sleep方法就没必要去写!Thread.currentThread().isInterrupted()去检测中断信号,因为前者已经能响应中断。
3. 中断线程的最佳实践
在while中try/catch中存在以下问题:
public class MyThread implements Runnable {
@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted()) {
System.out.println(count++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThread());
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
0
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at core.stop.best.MyThread.run(MyThread.java:10)
at java.lang.Thread.run(Thread.java:748)
1
2
如上述代码所示,在while循环内try/catch时,主线程发出中断请求,子线程响应了中断并捕获异常,但是程序依然继续执行,那是因为一旦sleep方法响应中断,响应后就会把中断状态清除,所以再进行检查中断信号时是检查不到的。
针对while内try/catch存在的问题,最佳解决方案是如果run方法中调用了其他方法且方法中需要抛出异常,那么久在被调用方法签名上抛出异常,并在catch中恢复中断,代码如下:
public class MyThreadOne implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("进入try");
throwMethod();
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
private void throwMethod() throws InterruptedException {
Thread.sleep(2000);
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThreadOne());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
进入try
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at core.stop.best.MyThreadOne.throwMethod(MyThreadOne.java:18)
at core.stop.best.MyThreadOne.run(MyThreadOne.java:9)
at java.lang.Thread.run(Thread.java:748)
上述代码的run方法中调用了throwMethod方法,throwMethod方法中调用了sleep方法并在方法签名上抛出异常,这样做的好处在于:
- 被调用方在方法签名上抛出异常,调用方必须捕获异常,这样调用方就可以处理这个异常,进而在catch中处理,比如重新标记中断状态,进而停止线程;
- 如果直接在被调用方处理异常,那么调用方就不知道是否有异常,就无法定位到异常发生的位置。
4. 停止线程的错误方法:stop、suspend和volatile boolean标记位
- stop方法会导致线程运行一半突然停止,没法完成一个基本单位的操作,造成脏数据
- suspend方法不会释放锁,如果没有被唤醒,容易造成死锁
- volatile boolean标记位
- 在非阻塞状态下停止线程
public class MyThreadVolatile implements Runnable {
private static volatile boolean canceled = false;
@Override
public void run() {
int count = 0;
while (!canceled) {
System.out.println(count++);
}
System.out.println("线程结束");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThreadVolatile());
thread.start();
Thread.sleep(500);
MyThreadVolatile.canceled = true;
}
}
- 在阻塞状态下停止线程
public class MyThreadVolatileSleep implements Runnable {
private static volatile boolean canceled = false;
@Override
public void run() {
while (!canceled) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程结束");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThreadVolatileSleep());
thread.start();
Thread.sleep(500);
MyThreadVolatileSleep.canceled = true;
}
}
因为sleep方法能响应interrupt方法中断线程,而不能响应volatile修饰的canceled关键字,所以在阻塞状态下,线程是无法中断的。
5. 停止线程相关重要函数解析
Thread类中判断是否已被中断的方法如下:
- interrupted方法,是Thread类的静态方法,判断完线程状态后会清除中断状态
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
- isInterrupted,不会清除中断状态
public boolean isInterrupted() {
return isInterrupted(false);
}
- Thread.interrupted()是个静态方法,该方法属于Thread类,而不属于子线程对象,所以该方法的目标对象是当前线程,即执行该方法的线程,而不管本方法由哪个对象调用的,比如:主线程中用子线程对象调用interrupted()方法,判断的是主线程,而不是子线程,代码如下:
public class MyThreadInterrupted implements Runnable {
@Override
public void run() {
for (;;) {
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThreadInterrupted());
thread.start();
thread.interrupt();
System.out.println(thread.isInterrupted());//子线程true
System.out.println(thread.interrupted());//主线程false
System.out.println(Thread.interrupted());//主线程false
thread.join();
System.out.println("主线程结束");
}
}
true
false
false