首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

并发编程之LockSupport原理剖析

LockSupport 用法简介

LockSupport 和 CAS 是Java并发包中很多并发工具控制机制的基础,它们底层其实都是依赖Unsafe实现。

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。LockSupport 提供park()和unpark()方法实现阻塞线程和解除线程阻塞,LockSupport和每个使用它的线程都与一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit, 也就是将1变成0,同时park立即返回。再次调用park会变成block(因为permit为0了,会阻塞在这里,直到permit变为1), 这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累。

park()和unpark()不会有 “Thread.suspend和Thread.resume所可能引发的死锁” 问题,由于许可的存在,调用 park 的线程和另一个试图将其 unpark 的线程之间的竞争将保持活性。

如果调用线程被中断,则park方法会返回。同时park也拥有可以设置超时时间的版本。

需要特别注意的一点:park 方法还可以在其他任何时间“毫无理由”地返回,因此通常必须在重新检查返回条件的循环里调用此方法。从这个意义上说,park 是“忙碌等待”的一种优化,它不会浪费这么多的时间进行自旋,但是必须将它与 unpark 配对使用才更高效。

三种形式的 park 还各自支持一个 blocker 对象参数。此对象在线程受阻塞时被记录,以允许监视工具和诊断工具确定线程受阻塞的原因。(这样的工具可以使用方法 getBlocker(java.lang.Thread) 访问 blocker。)建议最好使用这些形式,而不是不带此参数的原始形式。在锁实现中提供的作为 blocker 的普通参数是 this。

看下线程dump的结果来理解blocker的作用。

从线程dump结果可以看出:

有blocker的可以传递给开发人员更多的现场信息,可以查看到当前线程的阻塞对象,方便定位问题。所以java6新增加带blocker入参的系列park方法,替代原有的park方法。

看一个Java docs中的示例用法:一个先进先出非重入锁类的框架

classFIFOMutex {

private finalAtomicBooleanlocked=newAtomicBoolean(false);

private finalQueuewaiters

=newConcurrentLinkedQueue();

public voidlock() {

booleanwasInterrupted =false;

Thread current = Thread.currentThread();

waiters.add(current);

// Block while not first in queue or cannot

acquire lock

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 voidunlock() {

locked.set(false);

LockSupport.unpark(waiters.peek());

}

}LockSupport 源码解读

LockSupport中主要的两个成员变量:

// Hotspot implementation via intrinsics API

private static finalsun.misc.UnsafeUNSAFE;

private static final longparkBlockerOffset;

再来看parkBlockerOffset:

parkBlocker就是第一部分说到的用于记录线程被谁阻塞的,用于线程监控和分析工具来定位原因的,可以通过LockSupport的getBlocker获取到阻塞的对象。

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 newError(ex);}

}

从这个静态语句块可以看的出来,先是通过反射机制获取Thread类的parkBlocker字段对象。然后通过sun.misc.Unsafe对象的objectFieldOffset方法获取到parkBlocker在内存里的偏移量,parkBlockerOffset的值就是这么来的.

JVM的实现可以自由选择如何实现Java对象的“布局”,也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。 sun.misc.Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对 Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java 对象的某个字段。

为什么要用偏移量来获取对象?干吗不要直接写个get,set方法。多简单?

仔细想想就能明白,这个parkBlocker就是在线程处于阻塞的情况下才会被赋值。线程都已经阻塞了,如果不通过这种内存的方法,而是直接调用线程内的方法,线程是不会回应调用的。

2.LockSupport的方法:

可以看到,LockSupport中主要是park和unpark方法以及设置和读取parkBlocker方法。

private static voidsetBlocker(Thread t,Object arg) {

// Even though volatile, hotspot doesn't need a write barrier here.

UNSAFE.putObject(t,parkBlockerOffset,arg);

}

对给定线程t的parkBlocker赋值。

public staticObjectgetBlocker(Thread t) {

if(t ==null)

throw newNullPointerException();

returnUNSAFE.getObjectVolatile(t,parkBlockerOffset);

}

从线程t中获取它的parkBlocker对象,即返回的是阻塞线程t的Blocker对象。

接下来主查两类方法,一类是阻塞park方法,一类是解除阻塞unpark方法

阻塞线程

park()

public static voidpark(Object blocker) {

Thread t = Thread.currentThread();

setBlocker(t,blocker);

UNSAFE.park(false,0L);

setBlocker(t, null);

}

调用native方法阻塞当前线程。

parkNanos(long nanos)

public static voidparkNanos(longnanos) {

if(nanos >)

UNSAFE.park(false,nanos);

}

阻塞当前线程,最长不超过nanos纳秒,返回条件在park()的基础上增加了超时返回。

parkUntil(long deadline)

public static voidparkUntil(longdeadline) {

UNSAFE.park(true,deadline);

}

阻塞当前线程,知道deadline时间(deadline - 毫秒数)。

JDK1.6引入这三个方法对应的拥有Blocker版本。

park(Object blocker)

public static voidpark(Object blocker) {

Thread t = Thread.currentThread();

setBlocker(t,blocker);

UNSAFE.park(false,0L);

setBlocker(t, null);

}

1) 记录当前线程等待的对象(阻塞对象);

2) 阻塞当前线程;

3) 当前线程等待对象置为null。

parkNanos(Object blocker, long nanos)

public static voidparkNanos(Object blocker, longnanos) {

if(nanos >) {

Thread t = Thread.currentThread();

setBlocker(t,blocker);

UNSAFE.park(false,nanos);

setBlocker(t, null);

}

}

阻塞当前线程,最长等待时间不超过nanos毫秒,同样,在阻塞当前线程的时候做了记录当前线程等待的对象操作。

parkUntil(Object blocker, long deadline)

public static voidparkUntil(Object blocker, longdeadline) {

Thread t = Thread.currentThread();

setBlocker(t,blocker);

UNSAFE.park(true,deadline);

setBlocker(t, null);

}

阻塞当前线程直到deadline时间,相同的,也做了阻塞前记录当前线程等待对象的操作。

唤醒线程

unpark(Thread thread)

public static voidunpark(Thread thread) {

if(thread !=null)

UNSAFE.unpark(thread);

}

唤醒处于阻塞状态的线程Thread。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20190120G04IBW00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券