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 {

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") ;

}

}

java park unpark_LockSupport(park/unpark)源码分析相关推荐

  1. Java高并发之CountDownLatch源码分析

    概述 CountDownLatch 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助.简单来说,就是 CountDownLatch 内部维护了一个计数器,每个线程完成自己的操作之后都 ...

  2. Java并发编程之ThreadLocal源码分析

    1 一句话概括ThreadLocal   什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象的线程创建了一个独立的变量副本. 2 ThreadLocal使用场景   用一句话总结 ...

  3. 死磕java并发cas_死磕 java并发包之AtomicInteger源码分析

    问题 (1)什么是原子操作? (2)原子操作和数据库的ACID有啥关系? (3)AtomicInteger是怎么实现原子操作的? (4)AtomicInteger是有什么缺点? 简介 AtomicIn ...

  4. Java集合篇:LinkedList源码分析

    (注:本文内容基于JDK1.6) 一.概述: LinkedList与ArrayList一样实现List接口,只是ArrayList是List接口的大小可变数组的实现,LinkedList是List接口 ...

  5. java多线程系列:ThreadPoolExecutor源码分析

    前言 这篇主要讲述ThreadPoolExecutor的源码分析,贯穿类的创建.任务的添加到线程池的关闭整个流程,让你知其然所以然.希望你可以通过本篇博文知道ThreadPoolExecutor是怎么 ...

  6. java中Mark接口_JVM源码分析之Java对象头实现

    原标题:JVM源码分析之Java对象头实现 原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 "365篇原创计划"第十一篇. 今天呢!灯塔君跟大家讲: JVM源码分析之Ja ...

  7. java 并发包之 LongAdder 源码分析

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. LongAdder是java8中新增的原子类,在多线程环境中,它比AtomicLong性能要高出不少 ...

  8. java多线程系列:ThreadPoolExecutor源码分析,java基础面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

  9. Java设计模式学习以及底层源码分析

    源码在分支master 工厂模式 把具体创建产品的细节封装起来,你要什么产品,我给你什么产品即可. 简单工厂模式 工厂方法模式 缓存层:抽象类 抽象工厂模式 缓存层是:接口 原型模式 问题: 原型模式 ...

  10. java中jcl,spring-jcl 日志源码分析

    1.spring-jcl介绍 JCL全称:Jakarta Commons Logging spring-jcl 采用了设计模式中的"适配器模式",它对外提供统一的接口,然后在适配类 ...

最新文章

  1. 原生态纯JavaScript 100大技巧大收集
  2. threejs 纹理流动_Threejs多重纹理与过程纹理实现
  3. XGBoost相关知识-2
  4. Vue视频教程系列第三十七节-子路由地配置
  5. 20 款优秀的数据可视化工具,总有一款你用的到!
  6. hash地址_redis中的hash扩容、渐进式rehash过程
  7. 鸡蛋的硬度(信息学奥赛一本通-T1300)
  8. fragstats4.2使用
  9. 数据中心建筑及装修施工工序工艺管理要点
  10. windows下支持H265的rtmp ffplay播放器 ffmpeg的编译
  11. Tcplistener服务端与tcpclient客户端使用
  12. 浅析VO、DTO、DO、PO的概念、区别和用处
  13. 同个网络找不到计算机打印机共享,局域网共享打印机搜索不到怎么办 局域网共享打印机搜索不到解决方法...
  14. matlab 水文断面,基于MATLAB的河道横断面的绘制
  15. Hello!GitHub 好用好玩值得收藏的开源项目集合~
  16. 2019热门IT技术方向,你更中意哪个?
  17. Win7加密访问共享文件夹
  18. kubernetes 开发必须要知道的知识点—— API Group
  19. c语言键盘符号大全,求c语言各种符号 并且意义。。在键盘上没有的 如何打?...
  20. 通过 debug 检测屏幕颜色显示坏点、低格硬盘等等技巧

热门文章

  1. 小程序毕设作品之微信疫苗预约小程序毕业设计(7)中期检查报告
  2. 传奇人物张三的爱情困境
  3. verilog 学习笔记1
  4. Ceph知识树和技能树
  5. links for myself
  6. 关于github的license选择
  7. win10 任务栏图标左击无效,右击有效的处理办法
  8. defer=defer
  9. java实现注册登录版五子棋对战平台(超详细注释,内含人机实现)
  10. VantUi 底部Tabbar跳转页面的方法以及产生的Bug问题