中断机制
什么是中断机制?
首先
一个线程不应该由其他线程来强制中断或停止,而是应该有线程自己自行停止,自己来决定自己的命运。
所以,Thread.stop, Thead.suspend, Thead.resumer都已经被废弃了。
其次
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。
因此,Java提供了一种用于停止线程的协商机制–中断,即中断标识协商机制。
中断只是一种协商协作机制,Java中没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断表示设置成true
接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断
每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;
通过调用线程对象的interrupt方法将该线程的标识为设为true; 可以在别的线程中调用,也可以在自己的线程中调用。
- 通过volatile变量实现
- 通过AtomicBoolean实现
- 通过Thread类自带的中断api实例方法实现
public static void main(String[] args)
{
Thread t1 = new Thread(() -> {
while (true)
{
if(Thread.currentThread().isInterrupted())
{
System.out.println(Thread.currentThread().getName()+"\t isInterrupted()被修改为true,程序停止");
break;
}
System.out.println("t1 -----hello interrupt api");
}
}, "t1");
t1.start();
System.out.println("-----t1的默认中断标志位:"+t1.isInterrupted());
//暂停毫秒
try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
//t2向t1发出协商,将t1的中断标志位设为true希望t1停下来
new Thread(() -> {
t1.interrupt();
},"t2").start();
//t1.interrupt();
}
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
private static void m2_atomicBoolean()
{
new Thread(() -> {
while (true)
{
if(atomicBoolean.get())
{
System.out.println(Thread.currentThread().getName()+"\t atomicBoolean被修改为true,程序停止");
break;
}
System.out.println("t1 -----hello atomicBoolean");
}
},"t1").start();
//暂停毫秒
try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
atomicBoolean.set(true);
},"t2").start();
}
static volatile boolean isStop = false;
private static void m1_volatile()
{
new Thread(() -> {
while (true)
{
if(isStop)
{
System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序停止");
break;
}
System.out.println("t1 -----hello volatile");
}
},"t1").start();
//暂停毫秒
try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
isStop = true;
},"t2").start();
}
总结:当对一个线程,调用interrupt方法时:
① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true, 仅此而已。
被设置中断标志的线程将继续正常运行,不受影响。
所以,interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行。
②如果线程处于阻塞状态(例如处理sleep、wait、join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个interruptedException异常。
public static void main(String[] args)
{
//实例方法interrupt()仅仅是设置线程的中断状态位设置为true,不会停止线程
Thread t1 = new Thread(() -> {
for (int i = 1; i <=300; i++)
{
System.out.println("-----: "+i);
}
System.out.println("t1线程调用interrupt()后的的中断标识02:"+Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
System.out.println("t1线程默认的中断标识:"+t1.isInterrupted());//false
//暂停毫秒
try { TimeUnit.MILLISECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
t1.interrupt();//true
System.out.println("t1线程调用interrupt()后的的中断标识01:"+t1.isInterrupted());//true
try { TimeUnit.MILLISECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("t1线程调用interrupt()后的的中断标识03:"+t1.isInterrupted());
//????---false中断不活动的线程不会产生任何影响。
//ps: 两秒后线程已经执行完
}
中断协商案例深度解析
public static void main(String[] args)
{
Thread t1 = new Thread(() -> {
while (true)
{
if(Thread.currentThread().isInterrupted())
{
System.out.println(Thread.currentThread().getName()+"\t " +
"中断标志位:"+Thread.currentThread().isInterrupted()+" 程序停止");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// Thread.currentThread().interrupt();//没有它,程序不会停止,为什么要在异常处,再调用一次??
e.printStackTrace();
}
System.out.println("-----hello InterruptDemo3");
}
}, "t1");
t1.start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> t1.interrupt(),"t2").start();
}
/**
* 1 中断标志位,默认false
* 2 t2 ----> t1发出了中断协商,t2调用t1.interrupt(),中断标志位true
* 3 中断标志位true,正常情况,程序停止,^_^
* 4 中断标志位true,异常情况,InterruptedException,将会把中断状态将被清除,并且将收到InterruptedException 。中断标志位false
* 导致无限循环
*
* 5 在catch块中,需要再次给中断标志位设置为true,2次调用停止程序才OK
*/
官方描述
中断只是一种协商机制,修改中断标识位仅此而已,不是立刻stop打断
public static void main(String[] args)
{
//测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,
// 第二次再调用时中断状态已经被清除,将返回一个false。
System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted()); //false
System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());//false
System.out.println("----1");
Thread.currentThread().interrupt();// 中断标志位设置为true
System.out.println("----2");
System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());//true
System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());//false
LockSupport.park();
Thread.interrupted();//静态方法
Thread.currentThread().isInterrupted();//实例方法
}
静态方法interrupted将会清楚中断状态(源码传入的ClearInterrupted为true)
实例方法isInterrupted则不会(传入的参数ClearInterrupted为false)
总结:
线程中断相关的方法:
public void interrupt(); interrupt()方法是一个实例方法
它通知目标线程中断,也仅仅是设置目标线程的中断标志位为true.
public boolean isInterrupted(); isInterrupted()方法也是一个实例方法
它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志
public static boolean interrupted(), Thread类的静态方法interrupted()
返回当前线程的中断状态真实值(boolean类型)后会将当前线程的中断状态设为false, 此方法调用之后会清除当前线程的中断标志位的状态(将中断标志位置为false了),返回当前值并清零置false
线程等待和唤醒
LockSupport是用来创建和其他同步类的基本线程阻塞原语
文档
LockSupport中的 park() 和 unpark() 的作用分别是阻塞线程和解除被阻塞的线程
三种线程等待唤醒的方式
- 使用Object的wait()方法让线程等待,使用 Object中的notify()方法唤醒线程
- 使用JUC包中Condition的await方法让线程等待,使用signal()方法唤醒线程
- LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
Object
private static void syncWaitNotify()
{
Object objectLock = new Object();
new Thread(() -> {
try { TimeUnit.SECONDS.sleep(1); } 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();
}
wait()和notify()必须放在同步代码块或同步方法中,并且成对出现
必须现wait()在notify(),否则程序无法执行,无法唤醒
Condition
private static void lockAwaitSignal()
{
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
try { TimeUnit.SECONDS.sleep(1); } 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();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
lock.lock();
try
{
condition.signal();
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
}finally {
lock.unlock();
}
},"t2").start();
}
Condtion中的线程等待和唤醒方法,需要先获取锁
一定要先await再signal
LockSupport
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit)
但与Semaphores不同,许可证不会累积,最多只有一个
park()/park(Object blocker):阻塞,permit许可证默认没有不能方向,所以一开始调用park()方法当前线程就会阻塞,直到别的线程给当前线程发放permit,park方法才会被唤醒
unpark(Thread thread):唤醒,调用unpark(thread)方法后,就会将thread线程的许可证permit发放,会自动唤醒park线程,即之前阻塞中的LockSupport.park()方法会立即返回
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"+System.currentTimeMillis());
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t ----被唤醒"+System.currentTimeMillis());
}, "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();
}
LockSupport 天生无锁块要求
之前错误的先唤醒后后等待,LockSupport照样支持,先unpark再park相当于提前有了通行证unpark,park时就没有拦截。park和unpark必须一一对应,因为许可证不会累积,最多只有一个
总结
LockSupport是一个线程阻塞工具类,所有的方法都是静态的,可以让线程在任意位置阻塞,阻塞之后也有对于的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码(native标识的方法即调用底层C++、C代码)。
LockSupport提供的park() 和 unpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联。
每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会累加凭证。
理解
线程阻塞需要消耗凭证(permit),这个凭证最多只有一个。
当调用park方法时
- 如果有凭证,则会直接消耗掉这个凭证然后正常退出;
- 如果无凭证,就必须阻塞等待凭证可用;
而unpark则相反,它会增加一个凭证,但凭证最多只能有一个,累加无效。
为什么可以突破wait/notify的原有调用顺序?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的靠凭证消费,故不会阻塞。
先发放了凭证后续可以畅通无阻。
为什么唤醒两次后阻塞两次,但最终结果还是会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;
而调用两次park却需要消费两个凭证,证不够不能放行。