登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
24
Star
56
Fork
2
Java技术交流
/
Java技术提升库
代码
Issues
56
Pull Requests
0
Wiki
统计
流水线
服务
JavaDoc
PHPDoc
质量分析
Jenkins for Gitee
腾讯云托管
腾讯云 Serverless
悬镜安全
阿里云 SAE
Codeblitz
SBOM
我知道了,不再自动展开
更新失败,请稍后重试!
移除标识
内容风险标识
本任务被
标识为内容中包含有代码安全 Bug 、隐私泄露等敏感信息,仓库外成员不可访问
015、synchronized底层如何实现?什么是锁的升级、降级?
待办的
#I1VZ93
刘欣
成员
创建于
2020-09-21 04:05
上一讲对比和分析了synchronized和ReentrantLock,相信已经对线程安全,以及如何使用基本的同步机制有了基础,今天将深入了解synchronize 底层机制,分析其他锁实现和应用场景。 这篇讨论的问题是,synchronized底层如何实现?什么是锁的升级、降级? ### 考点分析 今天的问题主要是考察对 Java内置锁实现的掌握,也是并发的经典题目。 能够基础性地理解这些概念和机制,其实对于大多数并发编程已经足够了,毕竟大部分工程师未必会进行更底层、更基础的研发,很多时候解决的是知道与否,真正的提高还要靠实践踩坑。 后面会进一步分析∶ - 从源码层面,稍微展开一些 synchronized的底层实现,并补充一些上面答案中欠缺的细节,有同学反馈这部分容易被问到。如果你对Java 底层源码有兴趣,但还没有找到入手点,这里可以成为一个切入点。 - 理解并发包中java.utilconcurrent.lock 提供的其他锁实现,毕竟Java 可不是只有ReentrantLock 一种显式的锁类型,我会结合代码分析其使用。 ### 问题回答 在回答这个问题前,先简单复习一下上一讲的知识点。synchronized代码块是由一对儿monitorenter/monitorexit指令实现的,Monitor 对象是同步的基本实现单元。 在Java 6之前,Monitor的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。 现代的(Oracle)JDK中,JVM对此进行了大刀阔斧地改进,提供了三种不同的Monitor实现,也就是常说的三种不同的锁∶偏斜锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。 所谓锁的升级、降级,就是VM优化 synchronized运行的机制,当 JVM检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。 当没有竞争出现时,默认会使用偏斜锁。JVM会利用CAS操作(compare and swap),在对象头上的Mark Word部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。 如果有另外的线程试图锁定某个已经被偏斜过的对象,VM就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖CAS操作Mark Word来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。 我注意到有的观点认为 Java 不会进行锁降级。实际上据我所知,锁降级确实是会发生的,当VM进入安全点(SafePoint)的时候,会检查是否有闲置的Monitor,然后试图进行降级。 ### 后续扩展 在上一讲提到过synchronized是VM内部的Intrinsic Lock,所以偏斜锁、轻量级锁、重量级锁的代码实现,并不在核心类库部分,而是在JVM的代码中。 Java代码运行可能是解释模式也可能是编译模式,所以对应的同步逻辑实现,也会分散在不同模块下,比如,解释器版本就是∶ src/hotspot/share/interpreter/interpreterRuntime.cpp 为了简化便于理解,我这里会专注于通用的基类实现∶ src/hotspot/share/runtime 另外请注意,链接指向的是最新 JDK 代码库,所以可能某些实现与历史版本有所不同。 首先,synchronized的行为是JVM runtime的一部分,所以我们需要先找到 Runtime相关的功能实现。通过在代码中查询类似"monitor_enter"或"Monitor Enter",很直观的就可以定位到∶ - sharedRuntime.cpp/hpp,它是解释器和编译器运行时的基类。 - synchronizer.cpp/hpp,VM 同步相关的各种基础逻辑。 在 sharedRuntime.cpp 中,下面代码体现了synchronized的主要逻辑。 Handle h_obj(THREAD, obj); if(UseBiasedLocking){ //Retry fast entry if bias is revoked to avoid unnecessaryinflation Objectsynchronizer::fast_enter(h_obj,lock, true, CHECK); }else{ Objectsynchronizer::slow_enter(h obj,lock, CHECK); } 其实现可以简单进行分解∶ - UseBiasedLocking是一个检查,因为,在 ⅣM启动时,我们可以指定是否开启偏斜锁。 偏斜锁并不适合所有应用场景,撤销操作(revoke)是比较重的行为,只有当存在较多不会真正竞争的synchronized块儿时,才能体现出明显改善。实践中对于偏斜锁的一直是有争议的,有人甚至认为,当你需要大量使用并发类库时,往往意味着你不需要偏斜锁。从具体选择来看,我还是建议需要在实践中进行测试,根据结果再决定是否使用。 还有一方面是,偏斜锁会延缓IT预热的进程,所以很多性能测试中会显式地关闭偏斜锁,命令如下∶ -XX:-UseBiasedLocking - fast enter是我们熟悉的完整锁获取路径,slowenter则是绕过偏斜锁,直接进入轻量级锁获取逻辑。 那么 fastenter 是如何实现的呢?同样是通过在代码库搜索,我们可以定位到synchronizercpp。类似fast enter这种实现,解释器或者动态编译器,都是拷贝这段基础逻辑,所以如果我们修改这部分逻辑,要保证一致性。这部分代码是非常敏感的,微小的问题都可能导致死锁或者正确性问题。 void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPs)( if(UseBiasedLocking){ if(ISafepointsynchronize:is_at_safepoint()){ BiasedLocking:cCondition cond =BiasedlLocking:revoke and_rebias(obj, attempt_rebias if(cond BiasedLocking:BIAS_REVOKED_AND_REIASED){ return; } }else{ assert(!attempt_rebias, "can not rebias toward VM thread"); BiasedLocking::revoke_at_safepoint(obj); } assert(lobj->mark()->has_bias patten(),"biases should be revoked by now"); } slow_enter(obj,lock, THREAD); } 我来分析下这段逻辑实现∶ - biasedLocking定义了偏斜锁相关操作,revoke_and_rebias是获取偏斜锁的入口方法,revoke_at safepoint 则定义了当检测到安全点时的处理逻辑。 - 如果获取偏斜锁失败,则进入slowenter。 - 这个方法里面同样检查是否开启了偏斜锁,但是从代码路径来看,其实如果关闭了偏斜锁,是不会进入这个方法的,所以算是个额外的保障性检查吧。 另外,如果你仔细查看synchronizercpp里,会发现不仅仅是synchronized的逻辑,包括从本地代码,也就是JNI,触发的Monitor动作,全都可以在里面找到(jni_enter/jni_exit)。 关于biasedLocking的更多细节我就不展开了,明白它是通过CAS设置Mark Word就完全够用了,对象头中 Mark Word的结构,可以参考下图∶  顺着锁升降级的过程分析下去,偏斜锁到轻量级锁的过程是如何实现的呢? 我们来看看slowenter 到底做了什么。 void Objectsynchronizer:slow_enter(Handle obj,Basiclock* lock, TRAPs){ markOop mark = obj->mark(); if(mark->is_neutral()){ //将目前的Mark Word 复制到 Displaced Header 上lock-set_displaced_header(mark); //利用CAS 设置对象的 Mark word if(mark == obj()-cas_set_mark((markOop)lock,mark)){ TEVENT(slow_enter: release stack lock); return; } //检查存在竞争 }else if(mark->has_locker()&& THREAD->is_lock_owned((address)mark->locker())){ //清除 lock->set_displaced_header(NULL); return; } //重置 Displaced Header lock->set _displaced_header(markOopDesc::unused mark()); ObjectSynchronizer::inflate(THREAD,obj(),inflatecause_monitor_enter)->enter(THREAD); } 请结合我在代码中添加的注释,来理解如何从试图获取轻量级锁,逐步进入锁膨胀的过程。你可以发现这个处理逻辑,和我在这一讲最初介绍的过程是十分吻合的。 - 设置 Displaced Header,然后利用 casset_mark 设置对象 Mark Word,如果成功就成功获取轻量级锁。 - 否则 Displaced Header,然后进入锁膨胀阶段,具体实现在 inflate方法中。 今天就不介绍膨胀的细节了,我这里提供了源代码分析的思路和样例,考虑到应用实践,再进一步增加源代码解读意义不大,有兴趣的同学可以参考我提供的synchronizer.Cpp链接,例如∶ - deflatejidle_monitors是分析锁降级逻辑的入口,这部分行为还在进行持续改进,因为其逻辑是在安全点内运行,处理不当可能拖长 JVM停顿(STW,stop-the-world)的时间。 - fast exit或者slow exit是对应的锁释放逻辑。 前面分析了synchronized的底层实现,理解起来有一定难度,下面我们来看一些相对轻松的内容。我在上一讲对比了synchronized和 ReentrantLock,Java核心类库中还有其他一些特别的锁类型,具体请参考下面的图。  你可能注意到了,这些锁竟然不都是实现了Lock接口,ReadWritelock 是一个单独的接口,它通常是代表了一对儿锁,分别对应只读和写操作,标准类库中提供了再入版本的读写锁实现(ReentrantReadWriteLock),对应的语义和 ReentrantLock比较相似。 StampedLock 竟然也是个单独的类型,从类图结构可以看出它是不支持再入性的语义的,也就是它不是以持有锁的线程为单位。 为什么我们需要读写锁(ReadWritelock)等其他锁呢? 这是因为,虽然 ReentrantLock和 synchronized简单实用,但是行为上有一定局限性,通俗点说就是"太霸道",要么不占,要么独占。实际应用场景中,有的时候不需要大量竞争的写操作,而是以并发读取为主,如何进一步优化并发操作的粒度呢? Java 并发包提供的读写锁等扩展了锁的能力,它所基于的原理是多个读操作是不需要互斥的,因为读操作并不会更改数据,所以不存在互相干扰。而写操作则会导致并发一致性的问题,所以写线程之间、读写线程之间,需要精心设计的互斥逻辑。 下面是一个基于读写锁实现的数据结构,当数据量较大,并发读多、并发写少的时候,能够比纯同步版本凸显出优势。 public class RMSample{ private final Map<String,String>m= new TreeMap<>(); private final ReentrantReadMriteLock rwl= new ReentrantReaderiteLock(); private final Lock r=rwl.readLock(); private final Lock w= rwl.writelock(); public String get(String key){ P.1ock(); System.out.print1n("读锁锁定!"); try{ return m.get(key); }finally{ r.unlock(); } } public String put(String key,String entry){ w.lock(); System.out.println("写锁锁定!"); try{ return m.put(key, entry); }finally { w.unlock(); } //- } 在运行过程中,如果读锁试图锁定时,写锁是被某个线程持有,读锁将无法获得,而只好等待对方操作结束,这样就可以自动保证不会读取到有争议的数据。 读写锁看起来比synchronized的粒度似乎细一些,但在实际应用中,其表现也并不尽如人意,主要还是因为相对比较大的开销。 所以,JDK在后期引入了StampedLock,在提供类似读写锁的同时,还支持优化读模式。优化读基于假设,大多数情况下读操作并不会和写操作冲突,其逻辑是先试着修改,然后通过validate 方法确认是否进入了写模式,如果没有进入,就成功避免了开销;如果进入,则尝试获取读锁。请参考我下面的样例代码。 public class StampedSample{ private final StampedLock sl= new StampedLock(); void mutate(){ long stamp=sl.writelock(); try{ write(); }finally{ s1.unlockwrite(stamp); } } Data access(){ long stamp=sl.tryoptimisticRead(); Data data= read(); if(!sl.validate(stamp)){ stamp= sl. readLock(); try { data= read(); }finally { s1.unlockRead(stamp); } } return data; } //- } 注意,这里的writelock 和 unLockWrite一定要保证成对调用。 你可能很好奇这些显式锁的实现机制,Java 并发包内的各种同步工具,不仅仅是各种Lock,其他的如Semaphore、CountDownLatch,甚至是早期的FutureTask等,都是基于一种AOS框架。
上一讲对比和分析了synchronized和ReentrantLock,相信已经对线程安全,以及如何使用基本的同步机制有了基础,今天将深入了解synchronize 底层机制,分析其他锁实现和应用场景。 这篇讨论的问题是,synchronized底层如何实现?什么是锁的升级、降级? ### 考点分析 今天的问题主要是考察对 Java内置锁实现的掌握,也是并发的经典题目。 能够基础性地理解这些概念和机制,其实对于大多数并发编程已经足够了,毕竟大部分工程师未必会进行更底层、更基础的研发,很多时候解决的是知道与否,真正的提高还要靠实践踩坑。 后面会进一步分析∶ - 从源码层面,稍微展开一些 synchronized的底层实现,并补充一些上面答案中欠缺的细节,有同学反馈这部分容易被问到。如果你对Java 底层源码有兴趣,但还没有找到入手点,这里可以成为一个切入点。 - 理解并发包中java.utilconcurrent.lock 提供的其他锁实现,毕竟Java 可不是只有ReentrantLock 一种显式的锁类型,我会结合代码分析其使用。 ### 问题回答 在回答这个问题前,先简单复习一下上一讲的知识点。synchronized代码块是由一对儿monitorenter/monitorexit指令实现的,Monitor 对象是同步的基本实现单元。 在Java 6之前,Monitor的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。 现代的(Oracle)JDK中,JVM对此进行了大刀阔斧地改进,提供了三种不同的Monitor实现,也就是常说的三种不同的锁∶偏斜锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。 所谓锁的升级、降级,就是VM优化 synchronized运行的机制,当 JVM检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。 当没有竞争出现时,默认会使用偏斜锁。JVM会利用CAS操作(compare and swap),在对象头上的Mark Word部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。 如果有另外的线程试图锁定某个已经被偏斜过的对象,VM就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖CAS操作Mark Word来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。 我注意到有的观点认为 Java 不会进行锁降级。实际上据我所知,锁降级确实是会发生的,当VM进入安全点(SafePoint)的时候,会检查是否有闲置的Monitor,然后试图进行降级。 ### 后续扩展 在上一讲提到过synchronized是VM内部的Intrinsic Lock,所以偏斜锁、轻量级锁、重量级锁的代码实现,并不在核心类库部分,而是在JVM的代码中。 Java代码运行可能是解释模式也可能是编译模式,所以对应的同步逻辑实现,也会分散在不同模块下,比如,解释器版本就是∶ src/hotspot/share/interpreter/interpreterRuntime.cpp 为了简化便于理解,我这里会专注于通用的基类实现∶ src/hotspot/share/runtime 另外请注意,链接指向的是最新 JDK 代码库,所以可能某些实现与历史版本有所不同。 首先,synchronized的行为是JVM runtime的一部分,所以我们需要先找到 Runtime相关的功能实现。通过在代码中查询类似"monitor_enter"或"Monitor Enter",很直观的就可以定位到∶ - sharedRuntime.cpp/hpp,它是解释器和编译器运行时的基类。 - synchronizer.cpp/hpp,VM 同步相关的各种基础逻辑。 在 sharedRuntime.cpp 中,下面代码体现了synchronized的主要逻辑。 Handle h_obj(THREAD, obj); if(UseBiasedLocking){ //Retry fast entry if bias is revoked to avoid unnecessaryinflation Objectsynchronizer::fast_enter(h_obj,lock, true, CHECK); }else{ Objectsynchronizer::slow_enter(h obj,lock, CHECK); } 其实现可以简单进行分解∶ - UseBiasedLocking是一个检查,因为,在 ⅣM启动时,我们可以指定是否开启偏斜锁。 偏斜锁并不适合所有应用场景,撤销操作(revoke)是比较重的行为,只有当存在较多不会真正竞争的synchronized块儿时,才能体现出明显改善。实践中对于偏斜锁的一直是有争议的,有人甚至认为,当你需要大量使用并发类库时,往往意味着你不需要偏斜锁。从具体选择来看,我还是建议需要在实践中进行测试,根据结果再决定是否使用。 还有一方面是,偏斜锁会延缓IT预热的进程,所以很多性能测试中会显式地关闭偏斜锁,命令如下∶ -XX:-UseBiasedLocking - fast enter是我们熟悉的完整锁获取路径,slowenter则是绕过偏斜锁,直接进入轻量级锁获取逻辑。 那么 fastenter 是如何实现的呢?同样是通过在代码库搜索,我们可以定位到synchronizercpp。类似fast enter这种实现,解释器或者动态编译器,都是拷贝这段基础逻辑,所以如果我们修改这部分逻辑,要保证一致性。这部分代码是非常敏感的,微小的问题都可能导致死锁或者正确性问题。 void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPs)( if(UseBiasedLocking){ if(ISafepointsynchronize:is_at_safepoint()){ BiasedLocking:cCondition cond =BiasedlLocking:revoke and_rebias(obj, attempt_rebias if(cond BiasedLocking:BIAS_REVOKED_AND_REIASED){ return; } }else{ assert(!attempt_rebias, "can not rebias toward VM thread"); BiasedLocking::revoke_at_safepoint(obj); } assert(lobj->mark()->has_bias patten(),"biases should be revoked by now"); } slow_enter(obj,lock, THREAD); } 我来分析下这段逻辑实现∶ - biasedLocking定义了偏斜锁相关操作,revoke_and_rebias是获取偏斜锁的入口方法,revoke_at safepoint 则定义了当检测到安全点时的处理逻辑。 - 如果获取偏斜锁失败,则进入slowenter。 - 这个方法里面同样检查是否开启了偏斜锁,但是从代码路径来看,其实如果关闭了偏斜锁,是不会进入这个方法的,所以算是个额外的保障性检查吧。 另外,如果你仔细查看synchronizercpp里,会发现不仅仅是synchronized的逻辑,包括从本地代码,也就是JNI,触发的Monitor动作,全都可以在里面找到(jni_enter/jni_exit)。 关于biasedLocking的更多细节我就不展开了,明白它是通过CAS设置Mark Word就完全够用了,对象头中 Mark Word的结构,可以参考下图∶  顺着锁升降级的过程分析下去,偏斜锁到轻量级锁的过程是如何实现的呢? 我们来看看slowenter 到底做了什么。 void Objectsynchronizer:slow_enter(Handle obj,Basiclock* lock, TRAPs){ markOop mark = obj->mark(); if(mark->is_neutral()){ //将目前的Mark Word 复制到 Displaced Header 上lock-set_displaced_header(mark); //利用CAS 设置对象的 Mark word if(mark == obj()-cas_set_mark((markOop)lock,mark)){ TEVENT(slow_enter: release stack lock); return; } //检查存在竞争 }else if(mark->has_locker()&& THREAD->is_lock_owned((address)mark->locker())){ //清除 lock->set_displaced_header(NULL); return; } //重置 Displaced Header lock->set _displaced_header(markOopDesc::unused mark()); ObjectSynchronizer::inflate(THREAD,obj(),inflatecause_monitor_enter)->enter(THREAD); } 请结合我在代码中添加的注释,来理解如何从试图获取轻量级锁,逐步进入锁膨胀的过程。你可以发现这个处理逻辑,和我在这一讲最初介绍的过程是十分吻合的。 - 设置 Displaced Header,然后利用 casset_mark 设置对象 Mark Word,如果成功就成功获取轻量级锁。 - 否则 Displaced Header,然后进入锁膨胀阶段,具体实现在 inflate方法中。 今天就不介绍膨胀的细节了,我这里提供了源代码分析的思路和样例,考虑到应用实践,再进一步增加源代码解读意义不大,有兴趣的同学可以参考我提供的synchronizer.Cpp链接,例如∶ - deflatejidle_monitors是分析锁降级逻辑的入口,这部分行为还在进行持续改进,因为其逻辑是在安全点内运行,处理不当可能拖长 JVM停顿(STW,stop-the-world)的时间。 - fast exit或者slow exit是对应的锁释放逻辑。 前面分析了synchronized的底层实现,理解起来有一定难度,下面我们来看一些相对轻松的内容。我在上一讲对比了synchronized和 ReentrantLock,Java核心类库中还有其他一些特别的锁类型,具体请参考下面的图。  你可能注意到了,这些锁竟然不都是实现了Lock接口,ReadWritelock 是一个单独的接口,它通常是代表了一对儿锁,分别对应只读和写操作,标准类库中提供了再入版本的读写锁实现(ReentrantReadWriteLock),对应的语义和 ReentrantLock比较相似。 StampedLock 竟然也是个单独的类型,从类图结构可以看出它是不支持再入性的语义的,也就是它不是以持有锁的线程为单位。 为什么我们需要读写锁(ReadWritelock)等其他锁呢? 这是因为,虽然 ReentrantLock和 synchronized简单实用,但是行为上有一定局限性,通俗点说就是"太霸道",要么不占,要么独占。实际应用场景中,有的时候不需要大量竞争的写操作,而是以并发读取为主,如何进一步优化并发操作的粒度呢? Java 并发包提供的读写锁等扩展了锁的能力,它所基于的原理是多个读操作是不需要互斥的,因为读操作并不会更改数据,所以不存在互相干扰。而写操作则会导致并发一致性的问题,所以写线程之间、读写线程之间,需要精心设计的互斥逻辑。 下面是一个基于读写锁实现的数据结构,当数据量较大,并发读多、并发写少的时候,能够比纯同步版本凸显出优势。 public class RMSample{ private final Map<String,String>m= new TreeMap<>(); private final ReentrantReadMriteLock rwl= new ReentrantReaderiteLock(); private final Lock r=rwl.readLock(); private final Lock w= rwl.writelock(); public String get(String key){ P.1ock(); System.out.print1n("读锁锁定!"); try{ return m.get(key); }finally{ r.unlock(); } } public String put(String key,String entry){ w.lock(); System.out.println("写锁锁定!"); try{ return m.put(key, entry); }finally { w.unlock(); } //- } 在运行过程中,如果读锁试图锁定时,写锁是被某个线程持有,读锁将无法获得,而只好等待对方操作结束,这样就可以自动保证不会读取到有争议的数据。 读写锁看起来比synchronized的粒度似乎细一些,但在实际应用中,其表现也并不尽如人意,主要还是因为相对比较大的开销。 所以,JDK在后期引入了StampedLock,在提供类似读写锁的同时,还支持优化读模式。优化读基于假设,大多数情况下读操作并不会和写操作冲突,其逻辑是先试着修改,然后通过validate 方法确认是否进入了写模式,如果没有进入,就成功避免了开销;如果进入,则尝试获取读锁。请参考我下面的样例代码。 public class StampedSample{ private final StampedLock sl= new StampedLock(); void mutate(){ long stamp=sl.writelock(); try{ write(); }finally{ s1.unlockwrite(stamp); } } Data access(){ long stamp=sl.tryoptimisticRead(); Data data= read(); if(!sl.validate(stamp)){ stamp= sl. readLock(); try { data= read(); }finally { s1.unlockRead(stamp); } } return data; } //- } 注意,这里的writelock 和 unLockWrite一定要保证成对调用。 你可能很好奇这些显式锁的实现机制,Java 并发包内的各种同步工具,不仅仅是各种Lock,其他的如Semaphore、CountDownLatch,甚至是早期的FutureTask等,都是基于一种AOS框架。
评论 (
0
)
登录
后才可以发表评论
状态
待办的
待办的
进行中
已完成
已关闭
负责人
未设置
刘欣
liu-xin319
负责人
协作者
+负责人
+协作者
标签
未设置
标签管理
里程碑
01.JavaSE阶段面试题
未关联里程碑
Pull Requests
未关联
未关联
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
未关联
未关联
master
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
参与者(1)
1
https://gitee.com/beike-java-interview-alliance/java-interview.git
git@gitee.com:beike-java-interview-alliance/java-interview.git
beike-java-interview-alliance
java-interview
Java技术提升库
点此查找更多帮助
搜索帮助
Git 命令在线学习
如何在 Gitee 导入 GitHub 仓库
Git 仓库基础操作
企业版和社区版功能对比
SSH 公钥设置
如何处理代码冲突
仓库体积过大,如何减小?
如何找回被删除的仓库数据
Gitee 产品配额说明
GitHub仓库快速导入Gitee及同步更新
什么是 Release(发行版)
将 PHP 项目自动发布到 packagist.org
评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册