停止线程
停止线程的原则
使用interrupt()
来通知线程进行中断,而不是强制停止,在Java中若想停止线程最多就是告诉该线程该中断了,被告知的线程拥有是否停止线程的决定权;之所以这样设计是因为,要中断的线程通常需要完成一些保存工作再进行停止,若强行停止可能会出现混乱,比如数据丢失等情况
停止线程的相关方法
方法名 | 描述 |
---|---|
boolean isInterrupted() |
判断线程中断状态 |
static bolean interrupted() |
判断当前线程中断状态,并消除当前线程的中断标记位 |
void interrupt() |
中断线程 |
停止线程的正确方式
能够让线程正确停止方式只有以下两种情况
run()
方法所有代码执行完毕- 线程中抛出异常,并未进行异常的捕获
开发多线程时停止线程设计
中断检查
在run()
方法中添加收到中断信号后进行return返回即可
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < Integer.MAX_VALUE; i++) { //为了让程序运行时间足够长
if (Thread.currentThread().isInterrupted()) { //每次循环中检查是否收到中断信号
System.out.println("收到中断信号,进行保存工作");
return; //若收到中断信号该线程的就会执行结束
}
if (i % 1000000 == 0) {
System.out.println(i);
}
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt(); //1秒后传递中断信号
}
中断阻塞
阻塞方法列表
只有下面列表中的方法可以(由于重载方法比较多,将方法的参数省略):将当前线程进入阻塞状态后可响应到中断信号
Object.wait()
Thread.sleep()
Thread.join()
java.util.concurrent.BlockingQueue.take()/put()
java.util.concurrent.locks.Lock.lockInterruptibly()
java.util.concurrent.CountDownLatch.await()
java.util.concurrent.CyclicBarrier.await()
java.util.concurrent.Exchanger.exchange()
java.nio.channels.InterruptibleChannel
类的相关方法java.nio.channels.Selector
类的相关方法
单次阻塞
对run()
方法出现阻塞情况的代码,添加对InterruptedException
中断异常的捕获,当收到中断信号后就会进入该catch块,在catch块中return返回即可
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) { //若收到中断信号后会就进入该catch块
e.printStackTrace();
System.out.println("收到中断信号,进行保存工作");
return; //若没有return该线程并不会结束,除非后面没有代码了
}
for (int i = 0; i < Integer.MAX_VALUE; i++) {
if (i % 1000000 == 0) {
System.out.println(i);
}
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt(); //1秒后传递中断信号
}
多次阻塞
在迭代中的catch块相当于后面还有代码,之所以会出现这种情况是因为,Java中的Thread.sleep()
方法一旦响应中断就会将中断标记位清除,所以即时再次进行中断检查也无济于事
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
if (Thread.currentThread().isInterrupted()) return;
//若在catch块中没有return,即时在此处进行中断检查也无法停止线程
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("收到中断信号,进行保存工作");
e.printStackTrace();
//return; //若没有return该线程并不会结束
}
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt(); //1秒后传递中断信号
}
开发方法时停止线程的设计
不应该屏蔽中断,而是优先采用传递中断或恢复中断,否则会出现用户在调用时,线程无法向预计那样停止下来,比如下面代码这样
public class MyLib {
public void myMethod() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("myMethod执行完毕");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
MyLib myLib = new MyLib();
while (true) {
if (Thread.currentThread().isInterrupted()) return;
//用户中断检查,并return,但无法停止
myLib.myMethod();
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt(); //1秒后传递中断信号
}
}
传递中断
应该优先选择传递中断:将InterruptedException
中断异常的向外抛出,而并非捕获,那么用户在run()
方法中调用我们的方法时就会强制要求对该异常强制处理
public class MyLib {
public void myMethod() throws InterruptedException {
Thread.sleep(500);
System.out.println("myMethod执行完毕");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
MyLib myLib = new MyLib();
while (true) {
try {
myLib.myMethod();
} catch (InterruptedException e) {
e.printStackTrace();
return; //用户中断检查,并return
}
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt(); //1秒后传递中断信号
}
}
恢复中断
其次选择恢复中断:在catch块中再次调用interrupt()
方法来恢复Thread.sleep()
方法清除的中断标记
public class MyLib {
public void myMethod() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt(); //恢复中断
}
System.out.println("myMethod执行完毕");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
MyLib myLib = new MyLib();
while (true) {
if (Thread.currentThread().isInterrupted()) return; //用户中断检查,并return
myLib.myMethod();
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt(); //1秒后传递中断信号
}
}
停止线程的错误方式
使用弃用的线程停止方法
- 使用
suspend()
和resume()
方法并不像stop()
方法那样会破坏对象,但是会让一个线程带着锁进行挂起,很容易造成死锁,比如:唤醒该线程的线程需要这把锁就造成了死锁 - 使用被弃用的
stop()
,会导致线程运行一般突然停止,无法完成一个基本单位的操作,会造成脏数据
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("事务开始");
System.out.println("账户B:-100");
try {
Thread.sleep(500); //模拟转账延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("账户A:+100");
System.out.println("事务结束");
}
});
thread.start();
Thread.sleep(1000);
thread.stop(); //1秒后强制停止线程
}
volatile标记位方式
使用标记位方式在线程没有被阻塞的情况下是可行的,但是一旦线程进入阻塞状态后,只有线程被吵醒后才会停止线程,比如阻塞队列进入阻塞状态后,该线程就无法停止
public class MyRunnable implements Runnable {
private volatile boolean flag = false;
@Override
public void run() {
for (int i = 0; i < 5; i++) { //为了让程序运行时间足够长
if (flag) return; //每次循环中检查标记位
System.out.println(i); //每次循环休眠20秒
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
Thread.sleep(1000);
myRunnable.flag = true; //1秒后希望线程停止
}
}
Comments NOTHING