登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
3月21日 深圳|OpenClaw 线下实战沙龙:招聘、资讯、项目协同三大场景实操,VS ZeroClaw 横向对比评测,别再只会装,来现场跑通真实业务!
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
24
Star
55
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 帐号,请先登录后再操作。
立即登录
没有帐号,去注册