一、LockSupport 类功功能介绍
LockSupport是用于juc包下用于操作线程阻塞的工具类,可以看到底层都是通过CAS来支持。
AQS中实现线程挂起的方法,就是park,对应唤醒就是unpark。
LockSupport提供的是一个许可,如果存在许可,线程在调用park的时候,会立马返回,
此时许可也会被消费掉,如果没有许可,则会阻塞。调用unpark的时候,如果许可本身
不可用,则会使得许可可用。
许可只有一个,不能累加
二、LockSupport 类方法介绍
public class LockSupport {
private LockSupport() {} // Cannot be instantiated.
/**
* 设置线程阻塞的对象信息(即记录线程t 阻塞的原因 ),用于debug、jstack
*/
private static void setBlocker(Thread t, Object arg) {
// 即使hotspot是不稳定的,它也不需要写屏障。
UNSAFE.putObject(t, parkBlockerOffset, arg);//去除CAS的volatile优化
}
/**
* 唤醒当前阻塞的线程
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
/**
* 带有提示对象的阻塞
* 用于挂起(阻塞)当前线程,如果许可可用则调用立即返回,并消费掉许可,
* 若许可不可用则会阻塞。
* 如果许可是可用的,那么它被消耗并且调用立即返回;否则,当前线程会因为线程调度的目的而被禁用,
* 并处于休眠状态,直到发生以下三种情况之一才会恢复:
* 1)其他一些线程以当前线程为参数调用unpark;
* 2)在其他线程中 中断当前线程
* 3)调用虚假地(也就是说,毫无理由地)返回,即发生了不可预料的事情
*无论是什么情况返回,park方法本身都不会告知调用方返回的原因,所以调用的时候一般都会去判断返回的场景,根据场景做不同的处理
*
* park英文意思为停车。我们如果把Thread看成一辆车的话,park就是让车停下(阻塞线程),unpark就是让车启动然后跑起来(唤醒线程)
*
* park调用时,判断许可是否为true,如果是true,则继续往下执行;如果是false,则等待,直到许可为true
* unpark调用时,如果当前线程还未进入park,则许可为true
*
* 参数:
* blocker-- 负责该线程阻塞的同步对象(即 锁 对象),
*/
public static void park(Object blocker) {
//获取当前线程
Thread t = Thread.currentThread();
//记录线程阻塞的原因,底层就是 unsafe.putObject(),就是把对象 blocker 存储起来
setBlocker(t, blocker);
//执行park
UNSAFE.park(false, 0L);
//线程恢复后去掉阻塞原因
setBlocker(t, null);
}
/**
* 带超时时间(绝对时间)和阻塞原因的阻塞当前线程
* 用于挂起(阻塞/暂停)当前线程,该方法相对于上面的park方法,多了超时时间 nanos,表示若许可可用,则调用立即返回,并消费掉
* 许可,否则会把当前线程挂起,并等待超时时间 nanos,当过了 nanos 后若还没有可用许可(或还没有调用unPark唤醒当前线程),
* 则会把当前线程自动唤醒;
* 恢复的条件为
* 1:线程调用了unpark;
* 2:其它线程中断了线程;
* 3:发生了不可预料的事情;
* 4:过期时间到了
*
*/
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);
}
}
/**
* 带有超时时间(相对时间)和阻塞对象的线程阻塞操作
* 用于挂起当前线程直到某个时刻(时间点) deadline
*/
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
/**
* 获取阻塞原因
*/
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);
}
/**
*
*/
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;
}
private static final sun.misc.Unsafe 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;
//获取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); }
}
}
三、park/unpark 与 wait/notify 的区别?
1)park--unPark 类似于对象锁中的 wait-notify/notifyAll,也是用来阻塞/唤醒 线程,
但 wait--notify 只能先wait,后 notify,使用不当的话可能会引起死锁的问题,
且notify 唤醒的线程是随机的;
2)park--unpark 可以先执行 unpark(唤醒),后执行 park(阻塞),所以不回引起
死锁的问题,unpark 可以唤醒指定的线程;
3)wait -- notify 的调用需要依赖对象,但 park--unPark 不需要依赖对象,只需要
通过 LockSupport 类来调用,使用比较灵活;
4)park不会抛出InterruptedException异常,所以需要在park之后自行判断中断状态,
然后做额外的处理