JUC高级三:LockSupport与线程中断
1. 线程中断机制
1.1 什么是中断?
- 首先
- 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。
所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
- 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。
- 其次
- 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的机制——
中断
。 - 中断只是一种
协作机制
,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
- 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的机制——
1.2 如何中断一个线程?
- 若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;
- 接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;
通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
1.3 中断相关API方法
1.4 使用中断标识停止线程
1.4.1 方式一: 通过一个volatile变量实现
如果一个变量被volatile那么这个变量就具备可见性,在高并发情况下通过修改变量状态来决定程序和系统的运行
java">package site.zhourui.juc.interrupt;
import java.util.concurrent.TimeUnit;
public class InterruptDemo {
static volatile boolean isStop = false;
public static void main(String[] args) {
new Thread(() -> {
while(true)
{
if(isStop)
{
System.out.println("-----isStop = true,程序结束。");
break;
}
System.out.println("------hello isStop");
}
},"t1").start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
isStop = true;
},"t2").start();
}
}
执行效果:程序输出一秒------hello isStop
后检测到isStop为ture程序被中断
1.4.2 方式二:通过AtomicBoolean实现
AtomicBoolean就是原子性的Boolean,因为带有原子性天生就不需要加锁
java">package site.zhourui.juc.interrupt;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class InterruptDemo {
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) {
new Thread(() -> {
while(true)
{
if(atomicBoolean.get())
{
System.out.println("-----atomicBoolean.get() = true,程序结束。");
break;
}
System.out.println("------hello atomicBoolean");
}
},"t1").start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
atomicBoolean.set(true);
},"t2").start();
}
}
执行结果:程序输出一秒------hello atomicBoolean
后检测到atomicBoolean为ture程序被中断
1.4.3 方式三:通过Thread类自带的中断api方法实现(本章主要内容)
java">package site.zhourui.juc.interrupt;
import java.util.concurrent.TimeUnit;
public class InterruptDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("-----isInterrupted() = true,程序结束。");
break;
}
System.out.println("------hello Interrupt");
}
}, "t1");
t1.start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
t1.interrupt();//修改t1线程的中断标志位为true
},"t2").start();
}
}
执行结果:t2线程调用t1线程的interrupt()方法后t1线程被中断
1.5 interrupt()方法源码分析
1.6 isInterrupted()方法与interrupted源码分析
总结:
当对一个线程,调用 interrupt() 时:
- 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
- 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
1.7 当前线程的中断标识为true,是不是就立刻停止?
java">package site.zhourui.juc.interrupt;
import java.util.concurrent.TimeUnit;
public class InterruptDemo {
public static void main(String[] args) {
//中断为true后,并不是立刻stop程序
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 300; i++) {
System.out.println("------i: " + i);
}
System.out.println("t1.interrupt()调用之后02: "+Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
System.out.println("t1.interrupt()调用之前,t1线程的中断标识默认值: "+t1.isInterrupted());
try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
//实例方法interrupt()仅仅是设置线程的中断状态位设置为true,不会停止线程
t1.interrupt();
//活动状态,t1线程还在执行中
System.out.println("t1.interrupt()调用之后01: "+t1.isInterrupted());
try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
//非活动状态,t1线程不在执行中,已经结束执行了。
System.out.println("t1.interrupt()调用之后03: "+t1.isInterrupted());
}
}
执行结果:在执行interrupt方法后t1仍然在执行只是将中断标志位设为了true
1.8 InterruptedException怎么处理?
java">package site.zhourui.juc.interrupt;
import java.util.concurrent.TimeUnit;
public class InterruptDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("-----isInterrupted() = true,程序结束。");
break;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// Thread.currentThread().interrupt();//??????? //线程的中断标志位为false,无法停下,需要再次掉interrupt()设置true
e.printStackTrace();
}
System.out.println("------hello Interrupt");
}
}, "t1");
t1.start();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
t1.interrupt();//修改t1线程的中断标志位为true
},"t2").start();
}
}
执行结果:程序出现InterruptedException
后会一直执行不会停止,因为我们调用interrupt方法时t1线程正处于sleep状态,根据之前api的说明会爆出InterruptedException
并且会将我们中断状态清除
解决方法:在catch代码块中让中断标志为true,防止出现InterruptedException
异常程序无法停止
1.9 静态方法interrupted的理解
java">package site.zhourui.juc.interrupt;
import java.util.concurrent.TimeUnit;
public class InterruptDemo {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
System.out.println("111111");
Thread.currentThread().interrupt();///----false---> true
System.out.println("222222");
System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
}
}
执行结果:
2. LockSupport
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
2.1 所有方法
选中的为常用方法,该类无构造方法
2.2 3种让线程等待和唤醒的方法
2.2.1 方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
java">package site.zhourui.juc.interrupt;
import java.util.concurrent.TimeUnit;
public class LockSupportDemo {
static Object objectLock = new Object();
public static void main(String[] args) {
new Thread(() -> {
//暂停几秒钟线程
// try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLock){
System.out.println(Thread.currentThread().getName()+"\t"+"---come in");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒");
}
},"t1").start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName()+"\t"+"---发出通知");
}
},"t2").start();
}
}
执行结果:正常情况等待然后被唤醒
2.2.1.1 异常情况一:wait方法和notify方法,两个都去掉同步代码块
出现IllegalMonitorStateException
异常
2.2.1.2 异常情况二: 将notify在wait方法前面执行
线程t1将永远处于等待状态因为唤醒语句notify在wait方法前执行
2.2.2 方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
java">package site.zhourui.juc.interrupt;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockSupportDemo {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
new Thread(() -> {
//暂停几秒钟线程
// try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
lock.lock();
try
{
System.out.println(Thread.currentThread().getName()+"\t"+"---come in");
condition.await();
System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
},"t1").start();
new Thread(() -> {
lock.lock();
try
{
condition.signal();
System.out.println(Thread.currentThread().getName()+"\t"+"---发出通知");
}finally {
lock.unlock();
}
},"t2").start();
}
}
执行结果:正常状态线程等待然后被唤醒
总结:
- wait和notify方法必须要在同步块或者方法里面,且成对出现使用
- 先wait后notify才OK
2.2.2.1 异常情况一: 去掉lock/unlock
仍然出现IllegalMonitorStateException
异常
2.2.2.2 异常情况二:先signal后await
线程t1将永远处于等待状态
总结:
- Condtion中的线程等待和唤醒方法之前,需要先获取锁
- 一定要先await后signal,不要反了
Object和Condition使用的限制条件:
线程先要获得并持有锁,必须在锁块(synchronized或lock)中
必须要先等待后唤醒,线程才能够被唤醒
2.2.3 方式三:LockSupport类中的park等待和unpark唤醒
通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
2.2.3.1 主要方法
调用LockSupport.park()时
permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为零并返回。
调用LockSupport.unpark()时
调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
2.2.3.2 源码阅读
2.2.3.3 代码实例
java">package site.zhourui.juc.interrupt;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t" + "---come in");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒");
}, "t1");
t1.start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+"\t"+"---发出通知");
},"t2").start();
}
}
执行结果:
解决了方式一和方式二中的问题
无锁块报错问题以及先唤醒后等待程序一直等待问题
2.2.3.4 异常情况:多次park多次unpark
线程一直无法被唤醒,因为park颁发的许可证只能有一个,就算颁发多个也只能有一个,但是我们unpark两次就需要两个许可证所以无法唤醒线程t1