JDK源码那些事儿之LockSupport

前面一篇文章中说明了Object的阻塞唤醒机制,今天咱们要讲解另外一个类LockSupport,在AQS中你能看见它的身影,因此须要提早了解其实现和使用机制,便于后面深刻AQS的学习java

前言

JDK版本号:1.8.0_171

在源码阅读以前但愿你们先去阅读几遍注释,其中介绍了LockSupport的设计,实现和使用机制,这里进行简单说明下:多线程

  • 每一个使用LockSupport的线程都有一个permit(许可),假如permit可用则不进行阻塞。permit不可用时使用unpark可使得其处于可用状态,注意,这里permit是不能叠加的,也就是说只有一个
  • park和unpark提供了线程阻塞和线程唤醒操做,park支持中断响应,unpark操做能够在park以前调用,这样至关于先让permit处于可用状态, 那么再调用park就不会进行阻塞
  • park操做支持blocker对象参数,线程阻塞时会记录该对象,以容许监视和诊断工具肯定线程被阻塞的缘由,注释中推荐使用这种方式
  • LockSupport是被设计用来建立高级同步器工具类,对于大多数并发控制程序来讲用处不大

上述有些术语可能使人困惑,这里咱们通俗点说,首先须要理解permit(许可),这里也就是至关于一个变量标志,有兴趣可查看Hotspot源码并发

HotSpot Parker用condition和mutex维护了一个_counter变量,park时,变量_counter置为0,unpark时,变量_counter置为1

连续两次调用park操做,变量不会变成2,仍是1,也就是说的不能叠加,你能够本身写代码验证,由于维护的是一个变量标识更新,因此park和unpark的调用没有前后顺序限制:dom

  • 变量_counter为0(默认),若是调用park,则线程会被阻塞,若是调用unpark,则变量会置为1,唤醒被阻塞的线程(注意,阻塞的线程唤醒后会消耗掉又将变量置为0)
  • 变量_counter为1,若是调用park,则线程不会被阻塞,可是变量会被置为0,若是调用unpark,则变量仍是1

简单示例代码以下:ide

Thread test = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("start");
            LockSupport.park(this);// _counter为0,阻塞
            System.out.println("end");

        }
    });
    test.start();

    Thread.sleep(3000);
    System.out.println("ready notify");
    // 线程对应的_counter置为1,同时唤醒阻塞的线程,唤醒的线程消耗掉1置为0
    LockSupport.unpark(test);

FIFOMutex

在AbstractQueuedSynchronizer中使用了LockSupport实现线程阻塞和唤醒操做,因此有必要先进行了解,怎么经过LockSupport实现FIFO互斥锁呢?源码注释处已经提供了思路,非队首线程或者不能更新锁标识的都须要被阻塞,仍是挺巧妙的,能够好好理解理解工具

public class FIFOMutex {

    public static void main(String[] args) {
        FIFOMutex lock = new FIFOMutex();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread()+"111");
                lock.lock();
                System.out.println(Thread.currentThread()+"111");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread()+"222");
                lock.lock();
                System.out.println(Thread.currentThread()+"222");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread()+"333");
                lock.lock();
                System.out.println(Thread.currentThread()+"333");
            }
        }).start();
        Thread.sleep(1000);
        lock.unlock();
        Thread.sleep(1000);
        lock.unlock();
    }

    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);

        // Block while not first in queue or cannot acquire lock
        // 非队首线程或者CAS获取不到锁标识则进行阻塞
        while (waiters.peek() != current ||
                !locked.compareAndSet(false, true)) {
            LockSupport.park(this);
            if (Thread.interrupted()) // ignore interrupts while waiting
                wasInterrupted = true;
        }

        waiters.remove();
        if (wasInterrupted)          // reassert interrupt status on exit
            current.interrupt();
    }

    public void unlock() {
        locked.set(false);
        LockSupport.unpark(waiters.peek());
    }
}

常量

常量部分经过CAS来完成操做,没什么须要多说的,简单理解就好,不是重点学习

// Hotspot implementation via intrinsics API
    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); }
    }

构造方法

空的私有构造方法,不能被外部实例化ui

private LockSupport() {} // Cannot be instantiated.

重要方法

大量调用了UNSAFE的native方法,有兴趣的能够去找HotSpot源码来深刻学习,咱们这里仅作了解使用便可this

setBlocker

park相关方法中被调用,记录阻塞的对象,也就是监视和阻断工具查缘由时保存的对象线程

private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }

unpark

简单理解为唤醒对应的thread线程是不正确的,实际上,即便thread线程未调用park操做阻塞这里unpark操做也是能够进行的,使得thread线程的permit处于可用状态,那么以后thread线程调用park线程将不会被阻塞,由于permit可用,参考前言写些代码多理解理解

public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

park

在permit处于不可用状态时,阻塞当前线程,同时可传入blocker信息,同时注意被唤醒条件有如下三种:

  • 其余线程对当前线程调用unpark
  • 其余线程中断当前线程的执行
  • 不合逻辑的调用返回(这个不是很理解,有大神能够评论解释下)

被唤醒的缘由不会被返回,因此须要调用方自行检查是什么缘由

public static void park() {
        UNSAFE.park(false, 0L);
    }
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        // 这个地方在阻塞前保存了blocker信息
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        // 被唤醒以后被将blocker信息置空
        setBlocker(t, null);
    }

parkNanos

在permit处于不可用状态时,阻塞当前线程nanos毫秒,同时可传入blocker信息,唤醒机制和park()相似,除了多了一个超时条件,固然这里是超时自动唤醒的机制

public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }
    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);
        }
    }

parkUntil

在permit处于不可用状态时,阻塞当前线程到deadline时间点,同时可传入blocker信息,与parkNanos相似

public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }

getBlocker

获取线程t的blocker对象信息,也就是被阻塞前经过setBlocker(t, blocker)传入的对象信息

public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    }

nextSecondarySeed

这个方法是因为多线程随机数生成器ThreadLocalRandom的package访问权限限制不能被这个包下的类使用,复制了一份实现出来,在StampedLock中被使用,有兴趣能够去了解,之后会在StampedLock的源码中进行说明

/**
     * Returns the pseudo-randomly initialized or updated secondary seed.
     * Copied from ThreadLocalRandom due to package access restrictions.
     */
    static final int nextSecondarySeed() {
        int r;
        Thread t = Thread.currentThread();
        if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
            r ^= r << 13;   // xorshift
            r ^= r >>> 17;
            r ^= r << 5;
        }
        else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
            r = 1; // avoid zero
        UNSAFE.putInt(t, SECONDARY, r);
        return r;
    }

区别

那么LockSupport的阻塞唤醒机制和Object的阻塞唤醒机制有什么区别呢?

  • LockSupport不须要先进入对应的同步锁,可是Object的wait和notify须要先经过synchronized获取锁才能使用
  • LockSupport没有被局限在当前线程中,能够参考使用示例,而Object的wait和notify,在没有退出同步代码块以前,这个锁实际上仍是当前线程占用的,不论是否执行了wait和notify,只有在退出了同步代码块,这个锁才会真正的被释放

总结

本文分析了LockSupport的使用和源码,简单说明了Hotspot源码中对应的实现机制,方便各位理解,本质上而言仍是很好理解的,其实对于咱们而言更重要的在于使用,在线程阻塞唤醒机制上的使用须要你们多理解理解,下篇文章咱们就开始进行AQS的源码学习了,固然要好好理解下LockSupport

以上内容若有问题欢迎指出,笔者验证后将及时修正,谢谢

相关文章
相关标签/搜索