Java-并发-锁-LockSupport

版权声明:欢迎转载,请注明作者和出处 https://blog.csdn.net/baichoufei90/article/details/84639424

Java-并发-锁-LockSupport

0x01 摘要

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,他的两个主要方法park()unpark()的作用分别是阻塞线程和解除阻塞线程。本文简要分析下他的源码。

0x02 源码解析

2.1 类定义和构造方法

// 该类很耿直,就是个独立的 没有什么乱七八糟的继承关系
public class LockSupport {
    // 私有构造方法,不能实例化LockSupport
    private LockSupport() {}
}

2.2 初始化

// Hotspot implementation via intrinsics API
// UNSAFE实例
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 {
        // 只有根BootStrapClassLoader加载的类才能使用这个方式初始化UNSAFE,
        // 否则会报异常Exception in thread "main" java.lang.SecurityException: Unsafe
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        // 获取parkBlocker这个field在Thread类中的偏移位置
        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); }
}

2.3 重要方法

2.3.1 unpark

/**
 * 如果指定的线程处于park状态,就给他个许可让他不再阻塞
 * 否则,该线程的下一次park调用会保证不会被阻塞,但是多次调用就没用,只能保证一次
 * 当然,如果线程没开始运行,就没有任何保证
 *
 * @param thread 需要被unpark的线程对象,传null没有任何作用
 */
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

2.3.2 park

/**
 * 禁用当前线程已达到不可调度运行的目的,直到有许可 可用
 * 如果此方法时就有许可可用(在此之前已经用unpark给了本线程一个许可),那就会立刻消费这个许可并立刻返回;
 * 否则当前线程会被禁止线程调度,保持睡眠直到以下三种情况发生:
 * 1.某个其他的线程调用了unpark,并以当前线程为参数,给一个许可
 * 2.某个其他的线程对当前线程调用了interrupt方法,发了中断(此时不会抛出InterruptedException)
 * 3.The call spuriously (that is, for no reason) returns莫名奇妙的返回了?
 *
 * 因为该方法返回空,所以调用者不会知道是啥原因导致的返回,所以需要检查下原因,
 * 比如查下线程中断状态
 *
 * @param blocker 负责park此线程的同步对象
 * @since 1.6
 */
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

/**
 * 禁用当前线程已达到不可调度运行的目的,直到有许可 可用
 * 如果此方法时就有许可可用(在此之前已经用unpark给了本线程一个许可),那就会立刻消费这个许可并立刻返回;
 * 否则当前线程会被禁止线程调度,保持睡眠直到以下四种情况发生:
 * 1.某个其他的线程调用了unpark,并以当前线程为参数,给一个许可
 * 2.某个其他的线程对当前线程调用了interrupt方法,发了中断(此时不会抛出InterruptedException)
 * 3.The call spuriously (that is, for no reason) returns莫名奇妙的返回了?
 * 4.指定的等待时间消耗完毕
 *
 * 因为该方法返回空,所以调用者不会知道是啥原因导致的返回,所以需要检查下原因,
 * 比如查下线程中断状态
 *
 * @param blocker 负责park此线程的同步对象
 * @param nanos 等待时间阈值,纳秒单位
 * @since 1.6
 */
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类似,只不过传入的是毫秒级的绝对时间即时间戳
public static void parkUntil(long deadline) {
    UNSAFE.park(true, deadline);
}
    
/**
 * 禁用当前线程已达到不可调度运行的目的,直到有许可 可用
 * 
 * 如果此方法时就有许可可用(在此之前已经用unpark给了本线程一个许可),那就会立刻消费这个许可并立刻返回;
 * 否则当前线程会被禁止线程调度,保持睡眠直到以下三种情况发生:
 * 1.某个其他的线程调用了unpark,并以当前线程为参数,给一个许可
 * 2.某个其他的线程对当前线程调用了interrupt方法,发了中断(此时不会抛出InterruptedException)
 * 3.The call spuriously (that is, for no reason) returns莫名奇妙的返回了?
 * 
 * 因为该方法返回空,所以调用者不会知道是啥原因导致的返回,所以需要检查下原因,
 * 比如查下线程中断状态
 */
public static void park() {
    UNSAFE.park(false, 0L);
}

2.4 辅助方法

private static void setBlocker(Thread t, Object arg) {
    // 就算是volatile,hotspot vm 也不需要在这里使用读屏障
    // 将当前线程的parkBlocker设为arg
    UNSAFE.putObject(t, parkBlockerOffset, arg);
}

0x03 使用示例

import java.util.concurrent.locks.LockSupport;

public class LockParkDemo1 {
    private static Thread mainThread;
    public static void main(String[] args) {
        InnerThread it =  new LockParkDemo1().new InnerThread();
        Thread td = new Thread(it);
        mainThread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + " start it");
        td.start();
        System.out.println(Thread.currentThread().getName() + " block");
//        LockSupport.park(Thread.currentThread());
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + " continue");

    }
    class InnerThread implements Runnable{
        @Override
        public void run() {
            int count = 5;
            while(count>0){
                System.out.println("count=" + count);
                count--;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+" wakup others");
            LockSupport.unpark(mainThread);
        }
    }
}

程序输出结果如下:

main start it
main block
Thread-0 wakup others
main continue

0x04 LockSupport和wait/notify区别

  • 阻塞和唤醒是对于线程来说的,LockSupportpark/unpark更符合这个语义,以“线程”作为方法的参数, 语义更清晰,使用起来也更方便;

  • wait/notify的实现使得“线程”的阻塞/唤醒对线程本身来说是被动的,要准确的控制哪个线程、什么时候阻塞/唤醒很困难, 要不随机唤醒一个线程(notify)要不唤醒所有的(notifyAll)。

ReentrantLock的lock就是利用了LockSupport的相关方法来使线程阻塞或者唤醒的。

0xFF 参考文档

Unsafe类park,unpark详解

猜你喜欢

转载自blog.csdn.net/baichoufei90/article/details/84639424