java中的lock锁和lockSupport

325 阅读5分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

lock接口

lock只是一个接口,在JUC下的一个子包Locks包里。Lock需要显式地获取和释放锁,虽然不如隐式获取锁的便捷,但有了锁获取与释放的可操作性、可中断的获取锁及超时获取锁等同步特性。代码如下:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

void lock():获取锁,若锁不可用,则当前线程将会阻塞,直到获得锁;

void lockInterruptibly() throws InterruptedException:获取锁,当被中断或获取到锁才返回,若锁不可用,则当前线程被阻塞,直到获取锁或被中断;

boolean tryLock():尝试获取锁,并立即返回;true:获取锁成功;false:获取锁失败;

boolean tryLock(long time, TimeUnit unit) throws InterruptedException:尝试在指定的超时时间获取锁,当获取到锁时返回true;当超时或被中断时返回false;

Condition newCondition():返回一个和锁绑定的条件队列;在等待条件之前线程必须先获取当前锁,同时await()方法会原子地释放锁,并在返回之前重新获取到锁;

ReentrantLock和ReadWriteLock是此接口的实现;

void unlock():释放锁;

lockSupport工具类

LockSupport包含一组的公共静态方法,这些方法提供了基础的线程阻塞和唤醒功能,而LockSupport也成为构建同步组件的基础工具。下面看源码

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

    //设置阻塞当前线程的操作者,一般保存阻塞操作的线程Thread对象
    //parkBlocker对象为线程Thread.class的属性parkBlocker
    private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }

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

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

   //功能与park基本相同,此方法添加阻塞时间参数
    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);
        }
    }
    //功能与park基本相同,此方法添加阻塞到某个时间点的参数
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }

    //返回给定线程的parkBlocker参数
    public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    }

    
    //
    public static void park() {
        UNSAFE.park(false, 0L);
    }

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

    
    public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }

    

    //unsafe对象
    private static final sun.misc.Unsafe UNSAFE;
    //保存parkBlocker属性在Thread中的偏移量
    // 表示内存偏移地址
    private static final long parkBlockerOffset;
    // 表示内存偏移地址
    private static final long SEED;
    // 表示内存偏移地址
    private static final long PROBE;
    // 表示内存偏移地址
    private static final long SECONDARY;
    
    static {
        try {
            // 获取Unsafe实例
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            // 线程类类型
            Class<?> tk = Thread.class;
            // 获取Thread的parkBlocker字段的内存偏移地址
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            // 获取Thread的threadLocalRandomSeed字段的内存偏移地址
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            // 获取Thread的threadLocalRandomProbe字段的内存偏移地址
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            // 获取Thread的threadLocalRandomSecondarySeed字段的内存偏移地址
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }

}

LockSupport的核心函数都是基于Unsafe类中定义的park和unpark函数,对两个函数的说明如下: park函数,阻塞线程,并且该线程在下列情况发生之前都会被阻塞: 调用unpark函数,释放该线程的许可。该线程被中断。设置的时间到了。并且,当time为绝对时间时,isAbsolute为true,否则,isAbsolute为false。当time为0时,表示无限等待,直到unpark发生。 unpark函数,释放线程的许可,即激活调用park后阻塞的线程。这个函数不是安全的,调用这个函数时要确保线程依旧存活。

park函数

调用park函数时,首先获取当前线程,然后设置当前线程的parkBlocker字段,即调用setBlocker函数,之后调用Unsafe类的park函数,之后再调用setBlocker函数。同时park函数中要调用两次setBlocker函数, 因为调用park函数时,当前线程首先设置好parkBlocker字段,然后再调用Unsafe的park函数,此后,当前线程就已经阻塞了,等待该线程的unpark函数被调用,所以后面的一个setBlocker函数无法运行,unpark函数被调用,该线程获得许可后,就可以继续运行了,也就运行第二个setBlocker,把该线程的parkBlocker字段设置为null,这样就完成了整个park函数的逻辑。如果没有第二个setBlocker,那么之后没有调用park(Object blocker),而直接调用getBlocker函数,得到的还是前一个park(Object blocker)设置的blocker,显然是不符合逻辑的。总之,必须要保证在park(Object blocker)整个函数执行完后,该线程的parkBlocker字段又恢复为null。所以,park(Object)型函数里必须要调用setBlocker函数两次。

unpark函数

此函数表示如果给定线程的许可尚不可用,则使其可用。如果线程在 park 上受阻塞,则它将解除其阻塞状态。否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。若给定线程正常运行,则本次unpark会保证下次针对该线程的park不会阻塞该线程,当线程还未启动,umpark操作不会有任何作用。

许可.PNG

总结就是:许可(permit)的上限是1。 park: 没有许可的时候,permit 为 0 ,调用 park 会阻塞;有许可的时候,permit 为 1 , 调用 park 会扣除一个许可,然后返回。 unpark:没有许可的时候,permit 为 0 ,调用 unpark 会增加一个许可,因为许可上限是 1 , 所以调用多次也只会为 1 个。 线程初始的时候是没有许可的。 park 的当前线程如果被中断,会立即返回,并不会抛出中断异常。

写在最后

LockSupport的Park及Unpark是通过Unsafe的对应类实现的,而Unsafe的相关实现在jvm中,实现park及unpark的关键是Mutex互斥体、Condition信号量、_counter计数器,这点如果感兴趣可以自行查阅相关源码。