3.LockSupport与线程中断

LockSupport与线程中断

线程中断

​ 首先
​ 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。所以,Thread.stop,Thread.suspend, Thread.resume 都已经被废弃了。
​ 其次
​ 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制—中断,也即中断标识协商机制。
​ 中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现
​ 若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,此时究竞该做什么需要你自己写代码实现。
​ 每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断:通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

中断机制以及java相关api

如何中断一个线程

  1. 通过volatile变量实现
static volatile boolean isStop = false;
public static void main(String[] args) {
new Thread(() -> {
while (!isStop) {
System.out.println("---------t1---------");
}
System.out.println("---------t1 stop---------");
}, "t1").start();
new Thread(() -> {
isStop = true;
}, "t2").start();
}
结果
---------t1---------
---------t1---------
---------t1 stop---------
  1. 使用原子变量 AtomicBoolean
static volatile AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) {
new Thread(() -> {
while (!atomicBoolean.get()) {
System.out.println("---------t1---------");
}
System.out.println("---------t1 stop---------");
}, "t1").start();
new Thread(() -> {
atomicBoolean.set(true);
}, "t2").start();
}
结果:
---------t1---------
---------t1 stop---------
  1. 使用Thread自带中断方法
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("---------t1 stop---------");
break;
}
System.out.println("---------t1---------");
}
}, "t1");
t1.start();
new Thread(t1::interrupt, "t2").start();
// 或者t1自己设置标志位
// t1.interrupt();
执行结果
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1---------
---------t1 stop---------

中断三大方法说明:

public void interrupt()

​ 实例方法,Just to set the interrupt flag
​ 实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程

​ 具体来说,当对一个线程,调用interrupt()时:

​ 1、如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已:被设置中断标志的线程将继续正常运行,不受影响。所以,interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行。

​ 2、如果线程处于被阻塞状态(例如处于sleep, wait,join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。

这么使用可能会导致程序死循环

Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("---------t1 stop---------");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("---------t1---------");
}
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(t1::interrupt, "t2").start();
执行流程:
1、正常执行打印 ---------t1--------- 中断标志位 = false
2、t2 设置中断标志位 中断标志位 = true
3、t1遇到 Thread.sleep(200); 方法 抛出 InterruptedException 并且设置清除中断标志位 中断标志位 = false
4、最终导致程序无限循环
解决: 在sleep() catch 代码块中 重新设置中断标志位 为 true

public static boolean interrupted()
​ 静态方法,Thread.interrupted();判断线程是否被中断并清除当前中断状态。

​ 这个方法做了两件事:
1. 返回当前线程的中断状态,测试当前线程是否已被中断
2. 将当前线程的中断状态清零并重新设为false,清除线程的中断状态
​ 此方法有点不好理解,如果连续两次调用此方法,则第二次调用将返回false,因为连续调用两次 的结果可能不一样

public boolean isInterrupted()
​ 实例方法
​ 判断当前线程是否被中断〔通过检查中断标志位)

LockSupport

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport中的park()和 unpark()的作用分别是阻塞线程解除阻塞线程

三种线程等待和幻唤醒方法

  1. 使用object中的wait()方法让线程等待,使用object中的notify()方法唤醒线程方式
// 1、wait()方法和 notify()方法 只能用在 synchronized 同步代码块中
// object 的线程等待和唤醒方法
Object o = new Object();
new Thread(() -> {
System.out.println("++++++++++t1开始++++++++++");
try {
o.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1").start();
new Thread(() -> {
o.notify();
System.out.println("+++++++++++t2开启唤醒+++++++");
}, "t2").start();
结果:
++++++++++t1开始++++++++++
Exception in thread "t1" Exception in thread "t2" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at LockSupportDemo.lambda$main$0(LockSupportDemo.java:32)
at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at LockSupportDemo.lambda$main$1(LockSupportDemo.java:41)
at java.lang.Thread.run(Thread.java:748)
// 2、notify在wait方法前面先执行,程序无法唤醒
// object 的线程等待和唤醒方法
Object o = new Object();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o) {
System.out.println("++++++++++t1开始++++++++++");
try {
o.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("+++++++++++t1 被唤醒+++++++++++");
}
}, "t1").start();
new Thread(() -> {
synchronized (o) {
o.notify();
System.out.println("+++++++++++t2开启唤醒+++++++");
}
}, "t2").start();
结果:
+++++++++++t2开启唤醒+++++++
++++++++++t1开始++++++++++
(程序没有正常退出)
  1. 使用Juc包中Condition的await()方法让线程等待,使用signal()方法唤醒线程方式
// condition类的await signal方法
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
System.out.println("++++++++++t1开始++++++++++");
try {
condition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
System.out.println("+++++++++++t1 被唤醒+++++++++++");
}, "t1").start();
new Thread(() -> {
lock.lock();
condition.signal();
System.out.println("+++++++++++t2开启唤醒+++++++");
lock.unlock();
}, "t2").start();
结果:
++++++++++t1开始++++++++++
+++++++++++t2开启唤醒+++++++
+++++++++++t1 被唤醒+++++++++++
await signal 会出现与wait notify相同的问题
  1. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

    通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作

    LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
    LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),但与Semaphore不同的是,许可的累加上限是1。

    ​ park()阻塞 unpark() 唤醒

    park 源码
    public static void park() {
    UNSAFE.park(false, 0L);
    }
    public native void park(boolean var1, long var2);
    // boolean var1 =》 boolean isAbsolute() 是否拥有许可证 默认false =》 没有通行证
    // long var2 =》 long time 持续时间 默认 0l =》 不放行
    // permit许可证默认没有不能放行,所以一开始调park()方法当前线程就会阻塞,直到别的线程给当前线程的发放permit,park方法才会被唤醒。
    unpark() 源码
    public static void unpark(Thread thread) {
    if (thread != null)
    UNSAFE.unpark(thread);
    }
    // 要发许可证的线程 Thread thread
    // 正常执行
    Thread t1 = new Thread(() -> {
    System.out.println("++++++++++t1开始++++++++++");
    LockSupport.park();
    System.out.println("+++++++++++t1 被唤醒+++++++++++");
    }, "t1");
    t1.start();
    new Thread(() -> {
    LockSupport.unpark(t1);
    System.out.println("+++++++++++t2开启唤醒+++++++");
    }, "t2").start();
    结果
    ++++++++++t1开始++++++++++
    +++++++++++t2开启唤醒+++++++
    +++++++++++t1 被唤醒+++++++++++
    // 先执行 unpark 再执行park
    Thread t1 = new Thread(() -> {
    System.out.println("++++++++++t1开始++++++++++");
    try {
    TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    LockSupport.park();
    System.out.println("+++++++++++t1 被唤醒+++++++++++");
    }, "t1");
    t1.start();
    new Thread(() -> {
    try {
    TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    LockSupport.unpark(t1);
    System.out.println("+++++++++++t2开启唤醒+++++++");
    }, "t2").start();
    结果
    ++++++++++t1开始++++++++++
    +++++++++++t2开启唤醒+++++++
    +++++++++++t1 被唤醒+++++++++++
    程序完整执行----》先发许可证而后 t1线程持证上岗,不会阻塞
    // 发多次通行证
    Thread t1 = new Thread(() -> {
    System.out.println("++++++++++t1开始++++++++++");
    LockSupport.park();
    LockSupport.park();
    System.out.println("+++++++++++t1 被唤醒+++++++++++");
    }, "t1");
    t1.start();
    new Thread(() -> {
    // 多次发许可
    LockSupport.unpark(t1);
    LockSupport.unpark(t1);
    LockSupport.unpark(t1);
    System.out.println("+++++++++++t2开启唤醒+++++++");
    }, "t2").start();
    // 运行结果
    ++++++++++t1开始++++++++++
    +++++++++++t2开启唤醒+++++++
    (程序没有停止运行)

    总结
    LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
    LockSupport是一个线程阻寨工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
    LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程

    ​ LockSupport和每个使用它的线程都有一个许可(permit)关联。
    ​ 每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。
    ​ 也就是说
    ​ 线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。当调用park方法时
    ​ 如果有凭证,则会直接消耗掉这个凭证然后正常退出;*如果无凭证,就必须阻塞等待凭证可用;
    ​ 而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。

posted @   奶油炒白菜  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示