java LockSupport 方法使用 和总结

参考文章:
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 实现的。

一、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灵活度更低;

四、示例

park() 阻塞

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()时获取不到许可,所以进入阻塞状态。

park()、unpark()的执行不分先后顺序

如下代码:先释放许可,再获取许可,主线程能够正常终止。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() 也是无效的)。看下面的例子。

LockSupport 是不可重入的

如果一个线程连续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的时候,线程无法获取许可出现死锁。

LockSupport对应中断的响应性

@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。

猜你喜欢

转载自blog.csdn.net/xiaojin21cen/article/details/89918443