版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/my_momo_csdn/article/details/91617015
一、LockSupport
1.1 初识
- AQS中依赖的2个类是LockSupport和Unsafe,前者提供线程的(park/unpark操作,后者提供CAS操作),我们
先看看JDK源码中对LockSupport的描述
用于创建锁和其他同步类的基本线程阻塞原语。
1.2 本质
- LockSupport的park函数是将当前调用Thread阻塞,而unpark函数则是将指定线程Thread唤醒。
归根结底,LockSupport.park()和LockSupport.unpark(Thread thread)调用的是Unsafe中的native代码:
public static void park() {
UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
//对应的UNSAFE类中的代码是:
public native void park(boolean var1, long var2);
public native void unpark(Object var1);
- 与Object类的wait/notify机制相比,park/unpark有两个优点:
以thread为操作对象更符合阻塞线程的直观定义,操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。
1.3 许可
- 在上面的文字中,我使用了阻塞和唤醒,是为了和wait/notify做对比。其实park/unpark的设计原理核心是“许可”:park是等待一个许可,unpark是为某线程提供一个许可。如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。
- 关于许可的特点:
1.如果该许可可用,并且可在进程中使用,则调用 #park(...)将会立即返回,否则可能阻塞。
2.如果许可尚不可用,则可以调用#unpark(...) 使其可用。
3.许可不可重入,也就是说只能调用一次park(...) 方法,假如有一个许可,调用park会立即返回,再次调用则会阻塞。
4.许可不可叠加,“许可”是一次性的。比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态,换句话说unpark可以提供许可,但是最多也只能提
供一个许可,调用多次也没用,除非提供一个许可,然后这个许可被使用,然后再提供...。
1.4 示例
- 如下所示,演示先调用park,线程被阻塞,然后调用unpark,线程可以继续执行。
public class ParkSupportTest {
/**
*主线程3秒后才提供许可,因此Thread-1阻塞三秒,获得许可之后再继续执行
*/
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
thread1.start();
System.out.println("Main execute...");
SleepTools.second(3);
LockSupport.unpark(thread1);
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread [" + Thread.currentThread().getName()+"] start..."+new Date());
LockSupport.park();
System.out.println("Thread [" + Thread.currentThread().getName()+"] 1 park end..."+new Date());
}
}
}
打印:
Main execute...
Thread [Thread-1] start...Mon Jun 03 10:16:22 CST 2019
Thread [Thread-1] 1 park end...Mon Jun 03 10:16:25 CST 2019
- 如下所示,演示先调用unpark提供许可,再调用park,线程不会阻塞,说明许可是可以预先提供的,这和线程之间
的await和notify机制有很大的不同
public class ParkSupportTest1 {
/**
* 主线程先提供许可,因此Thread-1 Sleep了1秒,然后立马获得许可之后再继续执行,说明park没有阻塞,就继续执行了
*/
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
thread1.start();
LockSupport.unpark(thread1);
System.out.println("Main execute...");
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread [" + Thread.currentThread().getName() + "] start..." + new Date());
SleepTools.second(1);
LockSupport.park();
System.out.println("Thread [" + Thread.currentThread().getName() + "] 1 park end..." + new Date());
}
}
}
打印:
Main execute...
Thread [Thread-1] start...Mon Jun 03 11:04:45 CST 2019
Thread [Thread-1] 1 park end...Mon Jun 03 11:04:46 CST 2019
- 如下所示,演示先调用unpark多次,线程再多次调用park,第一次线程不会阻塞,但是后面线程会阻塞,
说明unpark提供的许可是不能叠加的,多次调用也只能提供一个许可。
public class ParkSupportTest2 {
/**
* 主线程调用unpark多次,Thread-1 调用park多次,第一次可以获取到许可不会阻塞,后面会阻塞,说明许可不能叠加,
* 在许可没有被使用掉的情况下,unpark最多只能提供一个许可
*/
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
thread1.start();
LockSupport.unpark(thread1);
LockSupport.unpark(thread1);
LockSupport.unpark(thread1);
System.out.println("Main execute...");
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread [" + Thread.currentThread().getName() + "] start..." + new Date());
SleepTools.second(1);
LockSupport.park();
System.out.println("Thread [" + Thread.currentThread().getName() + "] 1 park end..." + new Date());
LockSupport.park();
System.out.println("Thread [" + Thread.currentThread().getName() + "] 2 park end..." + new Date());
}
}
}
打印:
Main execute...
Thread [Thread-1] start...Mon Jun 03 11:04:10 CST 2019
Thread [Thread-1] 1 park end...Mon Jun 03 11:04:11 CST 2019
- 如下所示,线程先park阻塞,然后被unpark放行,然后线程再次park,线程会阻塞,说明park不像锁那样获取之后
可以可以重入,park会用掉许可,如果后面没有提供新的许可,再次park还会阻塞。
public class ParkSupportTest3 {
/**
* 主线程调用unpark一次,提供一个许可,Thread-1 调用park多次,第一次可以获取到许可不会阻塞,后面会阻塞,
* 说明获取许可之后,许可是一次性的
*/
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
thread1.start();
LockSupport.unpark(thread1);
System.out.println("Main execute...");
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread [" + Thread.currentThread().getName() + "] start..." + new Date());
SleepTools.second(1);
LockSupport.park();
System.out.println("Thread [" + Thread.currentThread().getName() + "] 1 park end..." + new Date());
LockSupport.park();
System.out.println("Thread [" + Thread.currentThread().getName() + "] 2 park end..." + new Date());
}
}
}
打印:
Main execute...
Thread [Thread-1] start...Mon Jun 03 11:03:45 CST 2019
Thread [Thread-1] 1 park end...Mon Jun 03 11:03:46 CST 2019
- 如下所示,主线程每间隔3秒提供一个许可,子线程则会每三秒获取到一个许可依次往下执行
public class ParkSupportTest4 {
/**
* 如下所示,主线程每间隔3秒提供一个许可,子线程则会每三秒获取到一个许可依次往下执行
*/
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
thread1.start();
System.out.println("Main execute...");
for (int i = 1; i <= 3; i++) {
LockSupport.unpark(thread1);
SleepTools.second(3);
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread [" + Thread.currentThread().getName() + "] start..." + new Date());
LockSupport.park();
System.out.println("Thread [" + Thread.currentThread().getName() + "] 1 park end..." + new Date());
LockSupport.park();
System.out.println("Thread [" + Thread.currentThread().getName() + "] 2 park end..." + new Date());
LockSupport.park();
System.out.println("Thread [" + Thread.currentThread().getName() + "] 3 park end..." + new Date());
}
}
}
打印:
Main execute...
Thread [Thread-1] start...Mon Jun 03 11:17:48 CST 2019
Thread [Thread-1] 1 park end...Mon Jun 03 11:17:48 CST 2019
Thread [Thread-1] 2 park end...Mon Jun 03 11:17:51 CST 2019
Thread [Thread-1] 3 park end...Mon Jun 03 11:17:54 CST 2019
1.5 小结
- park和unpark的使用有点类似于一个只能容纳一个信号的通知机制,好比有一个容器,里面只有只能容纳一个信号。
调用park的时候,如果容器里面有信号,线程就会放行并且把信号从容器移除,没有信号的话线程就阻塞,阻塞的
过程中如果容器中有信号了,线程会从容器移除这个信号并往下执行。
调用unpark的时候,逻辑比较简单,如果容器里面没有信号,就放一个进去,如果容器里面已经有了,那也没用,放不进去了。
- 因为LockSupport的源码是基于UNSAFE来实现的,因此源码可以看的不多,可以阅读最后的参考文章了解其在JDK中的
部分逻辑,主要还是了解其用法,便于阅读AQS的代码。