Java同步器之LockSupport源码解析

一、简介

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport中的park()unpark()的作用分别是阻塞线程和解除阻塞线程,而且park()unpark()不会遇到“Thread.suspendThread.resume所可能引发的死锁”问题。因为park()unpark()有许可的存在;调用park()的线程和另一个试图将其unpark()的线程之间的竞争将保持活性。

二、源码

2.1 构造方法

LockSupport类只提供了一个被private修饰的构造方法,意味着LockSupport不能在任何地方被实例化,但所有方法都是静态方法,可以在任意地方被调用。

 
/**
 * 私有构造
 */
private LockSupport() {}

2.2 属性

 
/**
 * 这个方法是由于多线程随机数生成器ThreadLocalRandom的package访问权限限制不能被这个包下的类使用,
 * 复制了一份实现出来,在StampedLock中被使用
 */
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;
}

/**
 * /**
 *  * 三种情况停止阻塞:
 *  * 1. 调用unpark
 *  * 2. 线程被中断
 *  * 3. 设置时间到了
 *  * @Param isAbsolute 是否绝对时间
 *  * @Param time 时间 为0时代表无线等待
 *  *
 * Unsafe.park(boolean isAbsolute,long time);
 *
 * /**
 *  * 释放某线程,需要保证释放时线程存活
 *  *
 * Unsafe.unpark(Thread thread);
 */
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实例 后续利用该实例获取偏移量 */
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        /** 创建一个Thread的class对象 后续利用反射获取字段 */
        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); }
}

2.3 blocker

2.3.1 blocker的作用

在分析LockSupport源码前,什么是blockerblocker的作用是什么?

Thread类的源码中,可以找到这样一个被volatile修饰的变量,LockSupport类中所有blocker的相关变量以及方法都是为这个parkBlocker变量服务的。

 
volatile Object parkBlocker;

当线程被阻塞时,如果该线程的parkBlocker变量不为空,则在打印堆栈异常时,控制台会打印输出具体阻塞对象的信息,方便错误排查,后文会对此进行演示

2.3.2 blocker相关方法

首先关注blocker相关方法:

 
/**
 * 设置blocker
 * 将线程t的parkBlocker字段的值更新为arg
 */
private static void setBlocker(Thread t, Object arg) {
    UNSAFE.putObject(t, parkBlockerOffset, arg);
}

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

源码中可以清晰的看到方法中使用了parkBlockerOffset,即在类初始化时获取的parkBlocker在内存中的偏移量,putObjectgetObjectVolatile方法采用地址加偏移量的方式从内存直接设置或获取parkBlockerUnsafe包下的方法可以直接操作内存,因此该类被命名为Unsafe),这样做的原因是因为线程被阻塞时无法被赋值或取值。

2.4 park

LockSupport为阻塞操作提供了两组三类方法,一组是不设置blocker的方法,另一组是设置blocker方法的,JDK推荐为Thread设置blocker方便调试。

 
/** 
 * 基础阻塞方法,无时限 
 */
public static void park(Object blocker) {
    /** 获取当前线程 */
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    /** 阻塞核心方法 直到被唤醒前不会执行下一语句 内部逻辑后文展开讨论 */
    UNSAFE.park(false, 0L);
    /** 线程被唤醒后parkBlocker重新置null */
    setBlocker(t, null);
}

/** 
 * 逻辑与park基本相同 阻塞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);
    }
}

/** 
 * 逻辑与park基本相同 阻塞到日期deadline为止 超过deadline日期后自动被唤醒  
 */
public static void parkUntil(Object blocker, long deadline) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(true, deadline);
    setBlocker(t, null);
}

/**
 * 无限等待其它线程将该线程unpark或中断该线程
 */
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);
}

2.5 unpark

unpark方法如下,UNSAFE.unpark为唤醒线程的核心方法

 
/**
 * 释放许可,使线程继续运行。
 * 如果线程没被阻塞,则下次park不会阻塞该线程?
 */
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

说明LockSupport是通过调用Unsafe函数中的接口实现阻塞和解除阻塞的。

2.6 底层实现

Unsafe的源码中可见,parkunpark都是native方法(意味着由C++底层实现)

 
void Parker::park(bool isAbsolute, jlong time) {
  // 先原子的将_counter的值设为0,并返回_counter的原值,如果原值>0说明有通行证,直接返回
  // 首先尝试能否获取许可
  // 利用原子操作设_counter(许可)为0,同时返回_counter原值
  // 原值为1时获取许可成功 直接return;
  if (Atomic::xchg(0, &_counter) > 0) return;

  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  JavaThread *jt = (JavaThread *)thread;
  //如果线程被中断 直接返回
  if (Thread::is_interrupted(thread, false)) {
    return;
  }

  timespec absTime;
  // 如果出现time小于0 或
  // 调用了parkUntil方法(只用调用parkUntil时isAbsolute为true)且time为0的情况
  // 意味着不需要尝试获取许可 直接返回
  if (time < 0 || (isAbsolute && time == 0) ) {
    return;
  }
  // 只有在java种调用了parkNanos或parkUntil方法才会进入此分支
  if (time > 0) {
    // 定时唤醒
    unpackTime(&absTime, isAbsolute, time);
  }

  ThreadBlockInVM tbivm(jt);

  // 如果线程被中断,直接返回
  // 如果没有被中断,且获取互斥锁失败,直接返回
  if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }

  int status ;
  // 如果_counter > 0, 不需要等待,这里再次检查_counter的值
  if (_counter > 0)  {
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
    // 插入写屏障
    OrderAccess::fence();
    return;
  }

  OSThreadWaitState osts(thread->osthread(), false);
  // 暂停Java线程
  jt->set_suspend_equivalent();

  assert(_cur_index == -1, "invariant");
  if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    // 线程进入阻塞状态 并等待_cond[_cur_index]信号
    status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
  } else {
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    // 线程进入限时阻塞状态
    status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
    if (status != 0 && WorkAroundNPTLTimedWaitHang) {
      pthread_cond_destroy (&_cond[_cur_index]) ;
      pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
    }
  }
  _cur_index = -1;
  _counter = 0 ;
  // 互斥锁释放
  status = pthread_mutex_unlock(_mutex) ;
  assert_status(status == 0, status, "invariant") ;
  OrderAccess::fence();
}

void Parker::unpark() {
  int s, status ;
  status = pthread_mutex_lock(_mutex);
  assert (status == 0, "invariant") ; 
  //保存原始许可值 用于后续判断
  s = _counter; 
  //许可置1
  _counter = 1;
  if (s < 1) {
     if (WorkAroundNPTLTimedWaitHang) {
        //唤醒等待线程
        status = pthread_cond_signal (_cond) ;
        assert (status == 0, "invariant") ;
        //释放锁操作
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant") ;
     } else {
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant") ;
        status = pthread_cond_signal (_cond) ;
        assert (status == 0, "invariant") ;
     }
  } else {
    //释放锁
    pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
  }
}

三、示例

对比下面的“示例1”和“示例2”可以更清晰的了解LockSupport的用法。

示例1

 
public class WaitTest1 {
 
    public static void main(String[] args) {
        ThreadA ta = new ThreadA("ta");
        synchronized(ta) { // 通过synchronized(ta)获取“对象ta的同步锁”
            try {
                System.out.println(Thread.currentThread().getName() + " start ta");
                ta.start();

                System.out.println(Thread.currentThread().getName() + " block");
                // 主线程等待
                ta.wait();

                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class ThreadA extends Thread {
        public ThreadA(String name) {
            super(name);
        }
        public void run() {
            synchronized (this) { // 通过synchronized(this)获取“当前对象的同步锁”
                System.out.println(Thread.currentThread().getName() + " wakup others");
                notify();    // 唤醒“当前对象上的等待线程”
            }
        }
    }
}

示例2

 
import java.util.concurrent.locks.LockSupport;

public class LockSupportTest1 {

    private static Thread mainThread;

    public static void main(String[] args) {
        ThreadA ta = new ThreadA("ta");
        // 获取主线程
        mainThread = Thread.currentThread();

        System.out.println(Thread.currentThread().getName() + " start ta");
        ta.start();

        System.out.println(Thread.currentThread().getName() + " block");
        // 主线程阻塞
        LockSupport.park(mainThread);

        System.out.println(Thread.currentThread().getName() + " continue");
    }

    static class ThreadA extends Thread {
        public ThreadA(String name) {
            super(name);
        }

        public void run() {
            System.out.println(Thread.currentThread().getName() + " wakup others");
            // 唤醒“主线程”
            LockSupport.unpark(mainThread);
        }
    }
}

运行结果

 
main start ta
main block
ta wakup others
main continue

四、总结

LocalSupport也是实现线程间通信的一种有效的方式。unparkpark之间不需要有严格的顺序。可以先执行unpark,之后再执行park。这样再编码的过程中就比使用wait/notify方法要简单很多。此外,LocalSUpport全部是采用UnSafe类来实现的。这个类通过使用park/unpark以及相关cas操作,就实现了javaJUC的各种复杂的数据结构和容器。而且效率非常高。

五、拓展

5.1 park/unpark与wait/notify的区别

  1. 先唤醒再阻塞操作,由LockSupport实现,即先调用unpark再调用park,则该线程不会被阻塞;由Object实现,即先调用notify再调用wait,则该线程会被阻塞。
  2. LockSupport允许在任意地方阻塞唤醒线程,Objectwait/notify必须在synchronized同步代码块内调用。因为park/unpark依赖许可量,wait/notify依赖锁。
  3. LockSupport允许唤醒指定线程,notify只能唤醒随机线程,notifyAll唤醒全部阻塞线程。
posted @ 2022-12-27 10:03  迷走神经  阅读(77)  评论(0编辑  收藏  举报