LockSupport源码分析

LockSupport源码分析


LockSupport 简介

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。LockSupport 提供park()和unpark()方法实现阻塞线程和解除线程阻塞,LockSupport和每个使用它的线程都与一个许可(permit)关联-----类似于一个二元信号量(只有1个许可证可供使用)

方法摘要

metheddescription
park()在许可可用之前阻塞当前线程。
park(Object blocker)在许可可用之前禁用当前线程,并指定阻塞对象。
parkNanos(long nanos)阻塞当前线程,最长不超过nanos纳秒。
parkNanos(Object blocker, long nanos)为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用,同时指定阻塞对象。
parkUntil(long deadline)阻塞当前线程直到deadline时间(绝对时间)。
parkUntil(Object blocker, long deadline)阻塞当前线程直到deadline时间,同时阻塞前记录当前线程等待对象的操作。
unpark(Thread thread)唤醒处于阻塞状态的线程Thread。
getBlocker(Thread t)获取阻塞线程t的Blocker对象。

源码分析

    private static final sun.misc.Unsafe UNSAFE;
    private static final long parkBlockerOffset;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }

先是通过反射机制获取Thread类的parkBlocker字段对象。然后通过sun.misc.Unsafe对象的objectFieldOffset方法获取到parkBlocker在内存里的偏移量。

    /**
     * The argument supplied to the current call to
     * java.util.concurrent.locks.LockSupport.park.
     * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
     * Accessed using java.util.concurrent.locks.LockSupport.getBlocker
     */
    volatile Object parkBlocker;

这个字段是Thread中的一个字段,可以理解为专门为LockSupport而设计的,它被用来记录线程是被谁堵塞的,当程序出现问题时候,通过线程监控分析工具可以找出问题所在。

为什么要用偏移量来获取对象,而不是通过Thread#getBlocker()获取?

parkBlocker是在线程处于阻塞的情况下才会被赋值。线程都已经阻塞了,如果不通过这种内存的方法,而是直接调用线程内的方法,线程是不会回应调用的。

parkNanos(long nanos)parkUntil(long deadline)的区别?

public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }
public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }

由上面源码可知,true 表示绝对时间,false表示相对时间

public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

setBlocker为什么要调用两次?

第一次调用setBlocker,为当前线程的parkBlocker设置blocker对象,然后调用park将当前线程挂起,此时当前线程已经阻塞了,等待unpark被调用,所以第二个setBlocker不能运行,当unpark被调用之后,该线程获得许可,可以继续运行,此时把parkBlocker设置为null。如果不调用第二个setBlocker,那么调用park之后,直接调用getBlocker函数,得到的还是上一次调用park(Object blocker)设置的blocker,所以需要必须要保证在park(Object blocker)整个函数执行完后,该线程的parkBlocker字段又恢复为null。

示例

官方文档的示例

一个简易的先进先出非重入锁(互斥信号量)的实现

class FIFOMutex {
   //锁状态标识
   private final AtomicBoolean locked = new AtomicBoolean(false);
   //记录等待线程队列
   private final Queue<Thread> waiters
     = new ConcurrentLinkedQueue<Thread>();

   public void lock() {
     boolean wasInterrupted = false;
     Thread current = Thread.currentThread();
     waiters.add(current);
     //如果当前线程不是等待线程队列第一个,或者locked状态已经是true,那么当前线程就要等待
     while (waiters.peek() != current ||
            !locked.compareAndSet(false, true)) {
        LockSupport.park(this);
        // 等待线程的中断线程标志位为true,就设置wasInterrupted为true,表示当前线程被中断
        if (Thread.interrupted())
          wasInterrupted = true;
     }
    //移除第一个元素。当前线程就是第一个元素,表示获得许可
     waiters.remove();
     //如果wasInterrupted为true,当前线程发出中断请求
     if (wasInterrupted)
        current.interrupt();
   }
   
   public void unlock() {
     locked.set(false);
     //唤醒当前线程队列中第一个元素
     LockSupport.unpark(waiters.peek());
   }
 }

jstack查看blocker

public static void main(String[] args) {
        Thread.currentThread().setName("my-thread");
        LockSupport.park("my-blocker");
}

jps查看当前线程id
image_1dgcnfj0s15u9evt7jo1u0ghdf9.png-10.3kB

jstack 39678
image_1dgcnhtt29vb1d2v11ed1fdl9kl16.png-21.6kB

可以看到线程my-thread处于WAITING状态,并且wait for 0x00000007957c01b0这个String对象

特性

  • 顺序无关
    先调用park,再调用unpark
public static void sequenceTest(){
        Thread thread = Thread.currentThread();
        LockSupport.park(thread);
        System.out.println("b1");
        LockSupport.park();
        System.out.println("b2");
}

输出
image_1dgeg4r2d1q4t16q1sda16meobe2d.png-1.6kB

先调用unpark,再调用park
todo 打算使用传统的生产者消费者模式来举例子


输出
在先调用unpark,再调用park时,仍能够正确实现同步

  • 不可重入
public static void noReentrantTest(){
        Thread thread = Thread.currentThread();
        LockSupport.unpark(thread);
        System.out.println("b1");
        LockSupport.park();
        System.out.println("b2");
        LockSupport.park();
        System.out.println("b3");
}

输出:
image_1dgefsmj1uk31p631fckinsrh20.png-1.4kB
可以看到b3并没有输出,说明第二次调用park时线程被阻塞了。

  • 响应中断
public static void interruptTest() throws Exception{

        Thread thread = new Thread(() -> {
            System.out.println("park begin");
            LockSupport.park();
            System.out.println("park end");
        });
        thread.start();
        Thread.sleep(2000);
        System.out.println("main interrupt");
        thread.interrupt();
        System.out.println("main end");
 }

输出
image_1dgeogqdcm561mlqdltrr21vco9.png-4.8kB

  • 面向线程

总结

1.park方法的调用一般要方法一个循环判断体里面。
如上述示例中的:

while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
    LockSupport.park(this);
}

之所以这样做,是为了防止线程被唤醒后不进行判断而意外继续向下执行,这其实是一种Guarded Suspension的多线程设计模式。
2.特性

  • 顺序无关:先调用unpark,再调用park时,仍能够正确实现同步,不会造成由wait/notify调用顺序不当所引起的阻塞,因此park/unpark相比wait/notify更加的灵活。

  • 不可重入:如果一个线程连续2次调用park,那么该线程一定会一直阻塞下去。

  • 响应中断:线程调用park阻塞之后,如果该线程被中断,此时interrupt起到的作用与unpark一样。

  • 面向线程LockSupportparkunpark线程直接操作的就是线程,更符合语义Objectwaitnotify它并不是直接对线程操作,它是被动的方法,它需要一个object来进行线程的挂起或唤醒。在调用对象的wait之前当前线程必须先获得该对象的锁,被唤醒之后需要重新获取到监视器才能继续执行。虽然都能更改线程状态为WAITING,但由于实现的机制不同,所以不会产生交集,就是park挂起线程,notify/notifyall是无法进行唤醒的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值