Java中LockSupport的使用


一、基本使用

LockSupport是JDK1.6中在java.util.concurrent中的子包locks中引入的一个比较底层的工具类,用来创建锁和其他同步工具类的基本线程阻塞原语。java锁和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通过调用 LockSupport .park()和 LockSupport .unpark()实现线程的阻塞和唤醒 的。 LockSupport 很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继 续 执行;如果许可已经被占用,当前线 程阻塞,等待获取许可。根据LockSupport 源码发现LockSupport的核心方法都是基于大名鼎鼎的sun.misc.Unsafe类中的park和unpark实现的。
LockSupport中的一些基本方法

/**
     * 阻塞当前线程,暂停当前线程的调度,同时是响应中断的;
     * 获取锁失败时线程也一直阻塞,直到拿到锁, 除非发生下面三种情况;
     * 锁的释放:
     * 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);
    }

二、LockSupport基本特征

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与其他锁的比较

基于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灵活度更低;

四、最后声明

由于个人工作经验欠缺、认识不足所以如文中有纰漏错误之处望诸位批评指正,本人感激不尽并及时改正;

参考文章
LockSupport解析与使用
java线程阻塞中断和LockSupport的常见问题


原文链接:https://blog.csdn.net/black_bird_cn/article/details/82624373