0
点赞
收藏
分享

微信扫一扫

JUC并发编程与源码分析笔记05-LockSupport与线程中断


内容简介

LockSupport

LockSupport是​​java.util.concurrent.locks​​包下的一个类,可以理解成是对Lock类的扩展。

线程中断机制

在​​java.lang.Thrread​​​里有三个方法:​​interrupt()​​​、​​interrupted()​​​、​​isInterrupted()​​。

线程中断机制

什么是中断机制

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

中断的相关三大API方法说明

​public void interrupt()​​​:设置线程的中断状态为true,发起一个协商,不会立刻停止线程。
​​​public static boolean interrupted()​​​:判断线程是否被中断并清除当前中断状态。这个方法做了两件事:1.返回当前线程的中断状态,测试当前线程是否已被中断;2.将当前线程的中断状态清零并设置成false,清除线程的中断状态。
如果连续两次调用此方法,则第二次调用将返回false ,因为连续调用两次的结果可能不一样。
​​​public boolean isInterrupted()​​:通过检查中断标志位,判断当前线程是否被中断。

大厂面试题中断机制考点

如何停止中断运行中的线程

通过一个volatile变量实现

public class InterruptDemo {
static volatile boolean stop = false;

public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (!stop) {
System.out.println(Thread.currentThread().getName() + "正在运行");
}
}, "thread1").start();
Thread.sleep(10);
new Thread(() -> {
stop = true;
}, "thread2").start();
}
}

通过AtomicBoolean实现

import java.util.concurrent.atomic.AtomicBoolean;

public class InterruptDemo {
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);

public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (!atomicBoolean.get()) {
System.out.println(Thread.currentThread().getName() + "正在运行");
}
}, "thread1").start();
Thread.sleep(10);
new Thread(() -> {
atomicBoolean.set(true);
}, "thread2").start();
}
}

通过Thread类自带的中断api实例方法实现

在需要中断的线程中,不断监听中断状态,一旦发生中断,就执行中断处理业务stop线程。

public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "正在运行");
}
}, "thread1");
thread1.start();
System.out.println("thread1默认标志位" + thread1.isInterrupted());
Thread.sleep(10);
new Thread(thread1::interrupt, "thread2").start();
}
}

对一个线程调用​​interrupt()​​​时,如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以,​​interrupt()​​​并不能真正的中断线程,需要被调用的线程自己配合才行。
如果线程处于阻塞状态(sleep,wait,join等),在别的线程中调用当前线程对象的​​​interrupt()​​​方法,线程将理解退出被阻塞状态,并抛出一个​​InterrException​​异常。

当前线程中断标识为true,是不是线程就立刻停止

public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 300;i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
System.out.println("thread1调用interrupt()后标志位2:" + Thread.currentThread().isInterrupted());
}, "thread1");
thread1.start();
System.out.println("thread1默认标志位:" + thread1.isInterrupted());
Thread.sleep(2);
thread1.interrupt();
System.out.println("thread1调用interrupt()后标志位1:" + thread1.isInterrupted());
Thread.sleep(2000);
System.out.println("thread1调用interrupt()后标志位3:" + thread1.isInterrupted());
}
}

public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "中断标志位:" + Thread.currentThread().isInterrupted() + "程序停止");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// 如果没有这句,thread1可能一直执行,当thread1处于阻塞状态的时候,其他线程调用了thread1.interrupt(),就会抛异常,而且会清空标志位的值
Thread.currentThread().interrupt();
e.printStackTrace();
}
System.out.println("hello world");
}
}, "thread1");
thread1.start();
Thread.sleep(1000);
thread1.interrupt();
}
}

静态方法Thread.interrupted(),谈谈你的理解

判断线程是否被中断并清除当前中断状态。
这个方法做了两件事:
返回当前线程中断状态,测试当前线程是否已被中断
将当前线程中断状态清零并设置为false,清除线程的中断状态

public class InterruptDemo {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
System.out.println(1);
Thread.currentThread().interrupt();
System.out.println(2);
// 因为执行了Thread.currentThread().interrupt();所以返回true,并且Thread.interrupted()还会把中断标志位置为false
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
}
}

总结

public static boolean interrupted() {
return currentThread().isInterrupted(true);
}

public boolean isInterrupted() {
return isInterrupted(false);
}

查看源码,就会发现​​isInterrupted()​​​和​​interrupted()​​​最终都调用了​​private native boolean isInterrupted(boolean ClearInterrupted);​​方法,不同点是传参不同,也就是说,这次操作是否需要清空标志位。

LockSupport是什么

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport中​​​park()​​​和​​unpark()​​的作用分别是阻塞线程和解除阻塞线程。

线程等待唤醒机制

3种让线程等待和唤醒的方法

  1. 使用Object中的​​wait()​​​方法让线程等待,使用Object中的​​notify()​​方法唤醒线程
  2. 使用JUC包中Condition的​​await()​​​方法让线程等待,使用​​signal()​​方法唤醒线程
  3. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

Object类中wait和notify方法实现线程等待和唤醒

public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
new Thread(() -> {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
object.wait();// 当前进程释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "被唤醒");
}
}, "thread1").start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (object) {
object.notify();
System.out.println(Thread.currentThread().getName() + "发出通知");
}
}, "thread2").start();
}
}

两个异常情况:
如果把上面代码的两个synchronized代码块注释掉,再次运行,程序会报错。结论:​​​wait()​​​和​​notify()​​​方法必须在synchronized里,也就必须持有该对象的锁,才能调用该对象的​​wait()​​​和​​notify()​​​方法。
尝试改变两个线程​​​wait()​​​和​​notify()​​​的顺序,也就是在thread1里wait之前,加一个sleep,先让thread2执行notify,再让thread1执行wait,程序在走完​​wait()​​​后,是没法被唤醒的,永远wait下去,程序不能正常退出。结论:必须先调用​​wait()​​​,再调用​​notify()​​。

Condition接口中的await后signal方法实现线程的等待和唤醒

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "开始执行");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println(Thread.currentThread().getName() + "被唤醒");
}, "thread1").start();
Thread.sleep(1000);
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "发出通知");
} finally {
lock.unlock();
}
}, "thread2").start();
}
}

两个异常情况:
把​​​lock.lock()​​​和​​lock.unlock()​​​注释掉,再次运行,程序会报错。结论:​​await()​​​和​​signal()​​​的执行,需要先获得锁。
尝试改变​​​await()​​​和​​signal()​​​的执行顺序,同样,也会出现程序不能正常停止的情况。结论:必须先调用​​await()​​​,再调用​​signal()​​。

上述两个对象Object和Condition使用的限制条件

  1. 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
  2. 必须先等待后唤醒,线程才能被成功唤醒

LockSupport类中的park等待和unpark唤醒

是什么

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

主要方法

阻塞:​​public static void park()​​​ 唤醒:​​public static void unpark(Thread thread)​

代码

import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "开始执行");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "被唤醒");
}, "thread1");
thread1.start();
Thread.sleep(1000);
new Thread(() -> {
LockSupport.unpark(thread1);
System.out.println(Thread.currentThread().getName() + "发出通知");
}, "thread2").start();
}
}

尝试之前的两个异常情况,发现程序可以正常执行。
使用LockSupport后,不需要把阻塞和唤醒放在锁内部,不需要控制阻塞和唤醒的顺序。
对于​​​LockSupport.park()​​​和​​LockSupport.unpark()​​​,可以理解为通行证的意思,​​park()​​​是上交通行证,​​unpark()​​​是发放通行证,而且通行证在手里最多只能有一张。
​​​LockSupport​​​是用来创建锁和其他同步类的基本线程阻塞原语。
​​​LockSupport​​​是一个线程阻寨工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,​​LockSupport​​​调用的​​Unsafe​​​中的native代码。
​​​LockSupport​​​提供​​park()​​​和​​unpark()​​​方法实现阻塞线程和解除线程阻塞的过程​​LockSupport​​​和每个使用它的线程都有一个许可关联。
每个线程都有一个相关的permit,permit最多只有一个,重复调用​​​unpark()​​​也不会积累凭证。
线程阻塞需要消耗凭证,这个凭证最多只有一个。
当调用​​​park()​​​方法时,直接消耗掉这个凭证然后正常退出,如果没有凭证,就必须阻塞等待凭证可用。
当调用​​​unpark()​​方法时,会增加一个凭证,但是凭证最多只能有一个,累计无效。


举报

相关推荐

0 条评论