concurrent包是基于AQS (AbstractQueuedSynchronizer)框架的,AQS框架借助于两个类:

  • Unsafe(提供CAS操作)
  • LockSupport(提供park/unpark操作)

因此,LockSupport非常重要。

两个重点

(1)操作对象

归根结底,LockSupport.park()和LockSupport.unpark(Thread thread)调用的是Unsafe中的native代码:

//LockSupport中
public static void park() {
UNSAFE.park(false, 0L);
}
//LockSupport中
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}

Unsafe类中的对应方法:

    //park
public native void park(boolean isAbsolute, long time); //unpack
public native void unpark(Object var1);

park函数是将当前调用Thread阻塞,而unpark函数则是将指定线程Thread唤醒。

与Object类的wait/notify机制相比,park/unpark有两个优点:

  • 以thread为操作对象更符合阻塞线程的直观定义
  • 操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。

(2)关于“许可”

在上面的文字中,我使用了阻塞和唤醒,是为了和wait/notify做对比。

  • 其实park/unpark的设计原理核心是“许可”:park是等待一个许可,unpark是为某线程提供一个许可。
    如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。

  • 有一点比较难理解的,是unpark操作可以再park操作之前。
    也就是说,先提供许可。当某线程调用park时,已经有许可了,它就消费这个许可,然后可以继续运行。这其实是必须的。考虑最简单的生产者(Producer)消费者(Consumer)模型:Consumer需要消费一个资源,于是调用park操作等待;Producer则生产资源,然后调用unpark给予Consumer使用的许可。非常有可能的一种情况是,Producer先生产,这时候Consumer可能还没有构造好(比如线程还没启动,或者还没切换到该线程)。那么等Consumer准备好要消费时,显然这时候资源已经生产好了,可以直接用,那么park操作当然可以直接运行下去。如果没有这个语义,那将非常难以操作。

  • 但是这个“许可”是不能叠加的,“许可”是一次性的。
    比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。

Unsafe.park和Unsafe.unpark的底层实现原理

在Linux系统下,是用的Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的。
mutex和condition保护了一个_counter的变量,当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。

源码:
每个Java线程都有一个Parker实例,Parker类是这样定义的:

class Parker : public os::PlatformParker {
private:
volatile int _counter ;
...
public:
void park(bool isAbsolute, jlong time);
void unpark();
...
}
class PlatformParker : public CHeapObj<mtInternal> {
protected:
pthread_mutex_t _mutex [1] ;
pthread_cond_t _cond [1] ;
...
}

可以看到Parker类实际上用Posix的mutex,condition来实现的。
在Parker类里的_counter字段,就是用来记录“许可”的。

  • park 过程

当调用park时,先尝试能否直接拿到“许可”,即_counter>0时,如果成功,则把_counter设置为0,并返回:

void Parker::park(bool isAbsolute, jlong time) {  

  // Ideally we'd do something useful while spinning, such
// as calling unpackTime(). // Optional fast-path check:
// Return immediately if a permit is available.
// We depend on Atomic::xchg() having full barrier semantics
// since we are doing a lock-free update to _counter. if (Atomic::xchg(0, &_counter) > 0) return;

如果不成功,则构造一个ThreadBlockInVM,然后检查_counter是不是>0,如果是,则把_counter设置为0,unlock mutex并返回:

ThreadBlockInVM tbivm(jt);
if (_counter > 0) { // no wait needed
_counter = 0;
status = pthread_mutex_unlock(_mutex);

否则,再判断等待的时间,然后再调用pthread_cond_wait函数等待,如果等待返回,则把_counter设置为0,unlock mutex并返回:

if (time == 0) {
status = pthread_cond_wait (_cond, _mutex) ;
}
_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
OrderAccess::fence();
  • unpark 过程

当unpark时,则简单多了,直接设置_counter为1,再unlock mutex返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程:

void Parker::unpark() {
int s, status ;
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
s = _counter;
_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") ;
}
}
 

转载:  https://www.jianshu.com/p/e3afe8ab8364

最新文章

  1. jQuery中事件与动画的总结
  2. 在IIS EXPRESS下运行一个visual studio 项目,跳转到另一个项目的解决方案。
  3. 1393: Robert Hood 旋转卡壳 凸包
  4. 基于webpack使用ES6新特性(转载)
  5. SpringMVC实现查询功能
  6. apache2添加模块和添加站点
  7. mysql找回密码
  8. Machine Learning : Pre-processing features
  9. Excel中的隐藏函数
  10. bzoj1043 下落的圆盘
  11. Ubuntu 14.04 LTS中怎样解决系统设置残缺的问题
  12. jQuery慢慢啃之特效(八)
  13. iOS 短信验证码倒计时按钮的实现
  14. [Hapi.js] Route parameters
  15. web攻击和防御措施
  16. Mac下安装git
  17. 技术Leader相关文章和思考
  18. 使用Selenium模块报错的解决办法 (FileNotFound,WebDriverException)
  19. 大数据学习(一)-------- HDFS
  20. IEC2017级_1-2班2次博客作业成绩说明

热门文章

  1. tomcat8.5部署管理控制台
  2. Linux监控命令之==&gt;sar
  3. Windows命令集锦
  4. pgAdmin III 使用图解
  5. 【FIORI系列】SAP OpenUI5 (SAPUI5) js框架简单介绍
  6. LeetCode算法题-Most Common Word(Java实现)
  7. Java与C#不同
  8. index.html(xpath素材)
  9. 蚁群算法解决TSP问题
  10. GmSSL Build with VS2017