参考文章:
https://blog.csdn.net/black_bird_cn/article/details/82624373
https://blog.csdn.net/secsf/article/details/78560013
LockSupport 是 JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞唤醒。
java锁和同步器框架的核心 AQS (AbstractQueuedSynchronizer),就是通过调用 LockSupport.park() 和 LockSupport.unpark() 实现线程的阻塞和唤醒的。
LockSupport 很类似于二元信号量(只有1个许可证可供使用),
如果这个许可还没有被占用,当前线程获取许可并继续执行;(park() 获取许可,否则线程阻塞)
如果许可已经被占用,当前线程阻塞,等待获取许可。( unpark() 发放许可,唤醒线程 )
根据 LockSupport 源码发现 LockSupport 的核心方法都是使用的 sun.misc.Unsafe
类中的 park 和 unpark 实现的。
/**
* 阻塞当前线程,暂停当前线程的调度,同时是响应中断的;
* 获取锁失败时线程也一直阻塞,直到拿到锁, 除非发生下面三种情况;
* 锁的释放:
* 1.调用unpark()立即释放锁;
* 2.当前线程中断(interrupt()),不会抛出异常,并且会立即释放锁;
* 3.到期时间,The call spuriously (that is, for no reason) returns.(不是很明白是什么鬼时间)
*/
public static void park() {
UNSAFE.park(false, 0L);
}
// park()的超扩展函数,超时单位为纳秒,如果超时自动释放
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
// park()的超扩展函数,超时单位为毫秒,如果超时自动释放;
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
/**
* 除了参数之外其他和park()一样;
* 参数:blocker,这个对象是用来记录线程被阻塞时被谁阻塞的,用于线程监控和分析工具来定位
* 根据源码可以看到的是参数blocker是在park之前先通过setBlocker()记录阻塞线程的发起者object,当线程锁被释放后再次清楚记录;
* 推荐使用该方法,而不是park(),因为这个函数可以记录阻塞的发起者,如果发生死锁方便查看
*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);//记录是哪个object对该线程发起的阻塞操作
UNSAFE.park(false, 0L);
setBlocker(t, null);//锁释放后,将存入是发起阻塞的object对象clear掉
}
// 看函数名字基本可以知道是设置超时[时间单位为纳秒]的,超时立即释放代码继续run,其他的和park(Object blocker)一样;
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
//该方法和parkNanos(Object blocker, long nanos)就是超时单位变化其他完全一样
public static void parkUntil(Object blocker, long deadline)
//手动释放锁的函数,与park相比释放锁的函数就只有一个足矣;
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
//查看阻塞线程的发起者,这个是和park(Object blocker)对应的,如果没有传入blocker自然就读不到
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
1、不可重入性
LockSupport是非重入锁,至于验证Demo网上都烂大街了
2、面向线程锁
这是LockSupport很重要的一个特征,也是与synchronized,object,reentrantlock最大的不同(个人理解),这样也就没有公平锁和非公平的区别了的,所以只是依靠LockSupport实现单例模式就有点困难啦。同时面向线程锁的特征在一定程度上降低代码的耦合度。
3、锁park与解锁unpark顺序可颠倒性
这个特征虽然是Java锁中比较独特是特征,其实这个特征也是基于LockSupport的状态[个人理解Blocke和Running]类似二元信号量(0和1)实现的,同样网上的Demo烂大街了 。
4、解锁unpark的重复性
unpark可重复是指在解锁的时候可以重复调用unpark;同样因为LockSupport基于二元锁状态重复调用unpark并不会影响到下一次park操作;
基于LockSupport提供的二中核心的功能,即:park阻塞 与 unpark 恢复运行状态;在此与之有类似功能的 Object的 wait/notify 和 ReentrantLock.Condition的 await()、signal() 进行比较;
其实LockSupport中的park有点类似Thread中的join、yield和sleep有重叠的功能那就是阻塞当前线程,不同的是这些都是直接调用native方法,不是LockSupport实现。
不同的点总结起来发现其实就在上文提到的LockSupport基本特征都是它与Object的wait/notify和ReentrantLock.Condition的await()、signal()的不同点;概述如下:
1.锁的实现机制不同
ReentrantLock面向的是线程,而Object和ReentrantLock.Condition都是典型的依赖一个对象实现锁机制;
2.锁的监视器依赖
ReentrantLock不需要依赖监视器,在源码中可以发现ReentrantLock并没有提供pubic的构造器,它的所有方法都是静态的;在Object和ReentrantLock.Condition都是需要new一个自身对象作为监视器介质;
3.粒度
粒度上很显然ReentrantLock粒度更细
4.使用灵活度
其实个人认为LockSupport虽然不需要依赖监视器一定程度上降低耦合而且解锁unpark和锁park顺序灵活,但是提供的函数过于单一,所以个人LockSupport灵活度更低;
package com.aop8.locksupport;
import java.util.concurrent.locks.LockSupport;
import org.junit.Test;
public class LockSupportDemo {
@Test
public void test1() throws Exception{
LockSupport.park();
System.out.println("block.");
}
}
运行该代码,可以发现主线程一直处于阻塞状态。因为许可默认是被占用的,调用park()时获取不到许可,所以进入阻塞状态。
如下代码:先释放许可,再获取许可,主线程能够正常终止。LockSupport许可的获取和释放,一般来说是对应的,如果多次unpark,只有一次park也不会出现什么问题,结果是许可处于可用状态。
@Test
public void test3() throws Exception{
Thread thread = Thread.currentThread();
LockSupport.unpark(thread);//释放许可
LockSupport.park();// 获取许可
System.out.println("b");
}
总结:
可以执行 unpark() ,但是 park() 只能执行一次,否则会处理阻塞、死锁(此时再调用 unpark() 也是无效的)。看下面的例子。
如果一个线程连续2次调用LockSupport.park(),那么该线程一定会一直阻塞下去。
@Test
public void test5() throws Exception {
Thread thread = Thread.currentThread();
System.out.println("a");
LockSupport.unpark(thread);
System.out.println("b");
LockSupport.park();
System.out.println("c");
LockSupport.park();
System.out.println("d");
}
@Test
public void test6() throws Exception {
Thread thread = Thread.currentThread();
System.out.println("a");
LockSupport.unpark(thread);
LockSupport.unpark(thread);
System.out.println("b");
LockSupport.park();
System.out.println("c");
LockSupport.unpark(thread);
System.out.println("d");
}
这段代码打印出a和b,不会打印c,因为第二次调用park的时候,线程无法获取许可出现死锁。
@Test
public void test7() throws Exception {
Thread threadA = new Thread(new Runnable() {
private int count = 0;
@Override
public void run() {
long start = System.currentTimeMillis();
long end = 0;
while ((end - start) <= 1000) {
count++;
end = System.currentTimeMillis();
}
System.out.println("after 1 second.count=" + count);
// 等待或许许可
LockSupport.park();
System.out.println("threadA over." + Thread.currentThread().isInterrupted());
}
},"threadA");
threadA.start();
Thread.sleep(2000);
// 中断线程
threadA.interrupt();
System.out.println("main over");
}
运行结果:
after 1 second.count=194785352
main over
threadA over.true
这说明线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException。