内容简介
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种让线程等待和唤醒的方法
- 使用Object中的
wait()
方法让线程等待,使用Object中的notify()
方法唤醒线程 - 使用JUC包中Condition的
await()
方法让线程等待,使用signal()
方法唤醒线程 - 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使用的限制条件
- 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
- 必须先等待后唤醒,线程才能被成功唤醒
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()
方法时,会增加一个凭证,但是凭证最多只能有一个,累计无效。