12讲多线程之锁优化(上):深⼊了解Synchronized同步锁的优化⽅法
你好,我是刘超。从这讲开始,我们就正式进⼊到第三模块——多线程性能调优。
在并发编程中,多个线程访问同⼀个共享资源时,我们必须考虑如何维护数据的原⼦性。在JDK1.5之前,Java是依靠
Synchronized关键字实现锁功能来做到这点的。Synchronized是JVM实现的⼀种内置锁,锁的获取和释放是由JVM隐式实现。
到了JDK1.5版本,并发包中新增了Lock接⼝来实现锁功能,它提供了与Synchronized关键字类似的同步功能,只是在使⽤时需要显示获取和释放锁。
Lock同步锁是基于Java实现的,⽽Synchronized是基于底层操作系统的Mutex Lock实现的,每次获取和释放锁操作都会带来
⽤户态和内核态的切换,从⽽增加系统性能开销。因此,在锁竞争激烈的情况下,Synchronized同步锁在性能上就表现得⾮常糟糕,它也常被⼤家称为重量级锁。
特别是在单个线程重复申请锁的情况下,JDK1.5版本的Synchronized锁性能要⽐Lock的性能差很多。例如,在Dubbo基于
Netty实现的通信中,消费端向服务端通信之后,由于接收返回消息是异步,所以需要⼀个线程轮询监听返回信息。⽽在接收消息时,就需要⽤到锁来确保request session的原⼦性。如果我们这⾥使⽤Synchronized同步锁,那么每当同⼀个线程请求锁资源时,都会发⽣⼀次⽤户态和内核态的切换。
到了JDK1.6版本之后,Java对Synchronized同步锁做了充分的优化,甚⾄在某些场景下,它的性能已经超越了Lock同步锁。这⼀讲我们就来看看Synchronized同步锁究竟是通过了哪些优化,实现了性能地提升。
Synchronized同步锁实现原理
了解Synchronized同步锁优化之前,我们先来看看它的底层实现原理,这样可以帮助我们更好地理解后⾯的内容。
通常Synchronized实现同步锁的⽅式有两种,⼀种是修饰⽅法,⼀种是修饰⽅法块。以下就是通过Synchronized实现的两种同步⽅法加锁的⽅式:
// 关键字在实例⽅法上,锁为当前实例
public synchronized void method1() {
// code
}
// 关键字在代码块上,锁为括号⾥⾯的对象
public void method2() { Object o = new Object(); synchronized (o) {
// code
}
}
下⾯我们可以通过反编译看下具体字节码的实现,运⾏以下反编译命令,就可以输出我们想要的字节码:
javac -encoding UTF-8 SyncTest.java //先运⾏编译class⽂件命令
javap -v SyncTest.class //再通过javap打印出字节⽂件
通过输出的字节码,你会发现:Synchronized在修饰同步代码块时,是由 monitorenter和monitorexit指令来实现同步的。进⼊
monitorenter 指令后,线程将持有Monitor对象,退出monitorenter指令后,线程将释放该Monitor对象。
public void method2(); descriptor: ()V flags: ACC_PUBLIC Code:
stack=2, locals=4, args_size=1 0: new #2
3: dup
4: invokespecial #1
7: astore_1
8: aload_1
9: dup
10: astore_2
11: monitorenter //monitorenter 指令
12: aload_2
13: monitorexit //monitorexit 指 令
14: goto 22
17: astore_3
18: aload_2
19: monitorexit
20: aload_3
21: athrow
22: return Exception table:
from to target type 12 14 17 any
17 20 17 any LineNumberTable:
line 18: 0
line 19: 8
line 21: 12
line 22: 22
StackMapTable: number_of_entries = 2 frame_type = 255 / full_frame /
offset_delta = 17
locals = [ class com/demo/io/SyncTest, class java/lang/Object, class java/lang/Object ] stack = [ class java/lang/Throwable ]
frame_type = 250 / chop / offset_delta = 4
再来看以下同步⽅法的字节码,你会发现:当Synchronized修饰同步⽅法时,并没有发现monitorenter和monitorexit指令,⽽
是出现了⼀个ACC_SYNCHRONIZED标志。
这是因为JVM使⽤了ACC_SYNCHRONIZED访问标志来区分⼀个⽅法是否是同步⽅法。当⽅法调⽤时,调⽤指令将会检查该
⽅法是否被设置ACC_SYNCHRONIZED访问标志。如果设置了该标志,执⾏线程将先持有Monitor对象,然后再执⾏⽅法。在
该⽅法运⾏期间,其它线程将⽆法获取到该Mointor对象,当⽅法执⾏完成后,再释放该Monitor对象。
public synchronized void method1(); descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED // ACC_SYNCHRONIZED 标志
Code:
stack=0, locals=1, args_size=1 0: return
LineNumberTable: line 8: 0
通过以上的源码,我们再来看看Synchronized修饰⽅法是怎么实现锁原理的。
JVM中的同步是基于进⼊和退出管程(Monitor)对象实现的。每个对象实例都会有⼀个Monitor,Monitor可以和对象⼀起创建、销毁。Monitor是由ObjectMonitor实现,⽽ObjectMonitor是由C++的ObjectMonitor.hpp⽂件实现,如下所示:
ObjectMonitor() {
_header = NULL;
_count = 0; //记录个数
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; //处于wait状态的线程,会被加⼊到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; FreeNext = NULL ;
_EntryList = NULL ; //处于等待锁block状态的线程,会被加⼊到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ; OwnerIsThread = 0 ;
}
当多个线程同时访问⼀段同步代码时,多个线程会先被存放在EntryList 集合中,处于block状态的线程,都会被加⼊到该列表。接下来当线程获取到对象的Monitor时,Monitor是依靠底层操作系统的Mutex Lock来实现互斥的,线程申请Mutex成功, 则持有该Mutex,其它线程将⽆法获取到该Mutex。
如果线程调⽤wait() ⽅法,就会释放当前持有的Mutex,并且该线程会进⼊WaitSet集合中,等待下⼀次被唤醒。如果当前线程顺利执⾏完⽅法,也将释放Mutex。
看完上⾯的讲解,相信你对同步锁的实现原理已经有个深⼊的了解了。总结来说就是,同步锁在这种实现⽅式中,因Monitor
是依赖于底层的操作系统实现,存在⽤户态与内核态之间的切换,所以增加了性能开销。
锁升级优化
为了提升性能,JDK1.6引⼊了偏向锁、轻量级锁、重量级锁概念,来减少锁竞争带来的上下⽂切换,⽽正是新增的Java对象头实现了锁升级功能。
当Java对象被Synchronized关键字修饰成为同步锁后,围绕这个锁的⼀系列升级操作都将和Java对象头有关。
Java对象头
在JDK1.6 JVM中,对象实例在堆内存中被分为了三个部分:对象头、实例数据和对⻬填充。其中Java对象头由Mark Word、指向类的指针以及数组⻓度三部分组成。
Mark Word记录了对象和锁有关的信息。Mark Word在64位JVM中的⻓度是64bit,我们可以⼀起看下64位JVM的存储结构是怎么样的。如下图所示:
锁升级功能主要依赖于Mark Word中的锁标志位和释放偏向锁标志位,Synchronized同步锁就是从偏向锁开始的,随着竞争越来越激烈,偏向锁升级到轻量级锁,最终升级到重量级锁。下⾯我们就沿着这条优化路径去看下具体的内容。
1.偏向锁
偏向锁主要⽤来优化同⼀线程多次申请同⼀个锁的竞争。在某些情况下,⼤部分时间是同⼀个线程竞争锁资源,例如,在创建
⼀个线程并在线程中执⾏循环监听的场景下,或单线程操作⼀个线程安全集合时,同⼀线程每次都需要获取和释放锁,每次操
作都会发⽣⽤户态与内核态的切换。
偏向锁的作⽤就是,当⼀个线程再次访问这个同步代码或⽅法时,该线程只需去对象头的Mark Word中去判断⼀下是否有偏向锁指向它的ID,⽆需再进⼊Monitor去竞争对象了。当对象被当做同步锁并有⼀个线程抢到了锁时,锁标志位还是01,“是否偏向锁”标志位设置为1,并且记录抢到锁的线程ID,表示进⼊偏向锁状态。
⼀旦出现其它线程竞争锁资源时,偏向锁就会被撤销。偏向锁的撤销需要等待全局安全点,暂停持有该锁的线程,同时检查该线程是否还在执⾏该⽅法,如果是,则升级锁,反之则被其它线程抢占。
下图中红线流程部分为偏向锁获取和撤销流程:
因此,在⾼并发场景下,当⼤量线程同时竞争同⼀个锁资源时,偏向锁就会被撤销,发⽣stop the word后, 开启偏向锁⽆疑
会带来更⼤的性能开销,这时我们可以通过添加JVM参数关闭偏向锁来调优系统性能,示例代码如下:
-XX:-UseBiasedLocking //关闭偏向锁(默认打开)
或
-XX:+UseHeavyMonitors //设置重量级锁
2.轻量级锁
当有另外⼀个线程竞争获取这个锁时,由于该锁已经是偏向锁,当发现对象头Mark Word中的线程ID不是⾃⼰的线程ID,就会进⾏CAS操作获取锁,如果获取成功,直接替换Mark Word中的线程ID为⾃⼰的ID,该锁会保持偏向锁状态;如果获取锁失
败,代表当前锁有⼀定的竞争,偏向锁将升级为轻量级锁。
轻量级锁适⽤于线程交替执⾏同步块的场景,绝⼤部分的锁在整个同步周期内都不存在⻓时间的竞争。
下图中红线流程部分为升级轻量级锁及操作流程:
3.⾃旋锁与重量级锁
轻量级锁CAS抢锁失败,线程将会被挂起进⼊阻塞状态。如果正在持有锁的线程在很短的时间内释放资源,那么进⼊阻塞状态的线程⽆疑⼜要申请锁资源。
JVM提供了⼀种⾃旋锁,可以通过⾃旋⽅式不断尝试获取锁,从⽽避免线程被挂起阻塞。这是基于⼤多数情况下,线程持有锁的时间都不会太⻓,毕竟线程被挂起阻塞可能会得不偿失。
从JDK1.7开始,⾃旋锁默认启⽤,⾃旋次数由JVM设置决定,这⾥我不建议设置的重试次数过多,因为CAS重试操作意味着
⻓时间地占⽤CPU。
⾃旋锁重试之后如果抢锁依然失败,同步锁就会升级⾄重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会进
⼊Monitor,之后会被阻塞在_WaitSet队列中。
下图中红线流程部分为⾃旋后升级为重量级锁的流程:
在锁竞争不激烈且锁占⽤时间⾮常短的场景下,⾃旋锁可以提⾼系统性能。⼀旦锁竞争激烈或锁占⽤的时间过⻓,⾃旋锁将会
导致⼤量的线程⼀直处于CAS重试状态,占⽤CPU资源,反⽽会增加系统性能开销。所以⾃旋锁和重量级锁的使⽤都要结合实际场景。
在⾼负载、⾼并发的场景下,我们可以通过设置JVM参数来关闭⾃旋锁,优化系统性能,示例代码如下:
-XX:-UseSpinning //参数关闭⾃旋锁优化(默认打开)
-XX:PreBlockSpin //参数修改默认的⾃旋次数。JDK1.7后,去掉此参数,由jvm控制
动态编译实现锁消除/锁粗化
除了锁升级优化,Java还使⽤了编译器对锁进⾏优化。JIT 编译器在动态编译同步块的时候,借助了⼀种被称为逃逸分析的技术,来判断同步块使⽤的锁对象是否只能够被⼀个线程访问,⽽没有被发布到其它线程。
确认是的话,那么 JIT 编译器在编译这个同步块的时候不会⽣成 synchronized 所表示的锁的申请与释放的机器码,即消除了锁的使⽤。在 Java7 之后的版本就不需要⼿动配置了,该操作可以⾃动实现。
锁粗化同理,就是在 JIT 编译器动态编译时,如果发现⼏个相邻的同步块使⽤的是同⼀个锁实例,那么 JIT 编译器将会把这⼏个同步块合并为⼀个⼤的同步块,从⽽避免⼀个线程“反复申请、释放同⼀个锁“所带来的性能开销。
减⼩锁粒度
除了锁内部优化和编译器优化之外,我们还可以通过代码层来实现锁优化,减⼩锁粒度就是⼀种惯⽤的⽅法。
当我们的锁对象是⼀个数组或队列时,集中竞争⼀个对象的话会⾮常激烈,锁也会升级为重量级锁。我们可以考虑将⼀个数组和队列对象拆成多个⼩对象,来降低锁竞争,提升并⾏度。
最经典的减⼩锁粒度的案例就是JDK1.8之前实现的ConcurrentHashMap版本。我们知道,HashTable是基于⼀个数组+链表实现的,所以在并发读写操作集合时,存在激烈的锁资源竞争,也因此性能会存在瓶颈。⽽ConcurrentHashMap就很很巧妙地 使⽤了分段锁Segment来降低锁资源竞争,如下图所示:
总结
JVM在JDK1.6中引⼊了分级锁机制来优化Synchronized,当⼀个线程获取锁时,⾸先对象锁将成为⼀个偏向锁,这样做是为 了优化同⼀线程重复获取导致的⽤户态与内核态的切换问题;其次如果有多个线程竞争锁资源,锁将会升级为轻量级锁,它适
⽤于在短时间内持有锁,且分锁有交替切换的场景;轻量级锁还使⽤了⾃旋锁来避免线程⽤户态与内核态的频繁切换,⼤⼤地提⾼了系统性能;但如果锁竞争太激烈了,那么同步锁将会升级为重量级锁。
减少锁竞争,是优化Synchronized同步锁的关键。我们应该尽量使Synchronized同步锁处于轻量级锁或偏向锁,这样才能提
⾼Synchronized同步锁的性能;通过减⼩锁粒度来降低锁竞争也是⼀种最常⽤的优化⽅法;另外我们还可以通过减少锁的持有时间来提⾼Synchronized同步锁在⾃旋时获取锁资源的成功率,避免Synchronized同步锁升级为重量级锁。
这⼀讲我们重点了解了Synchronized同步锁优化,这⾥由于字数限制,也为了你能更好地理解内容,⽬录中12讲的内容我拆成了两讲,在下⼀讲中,我会重点讲解Lock同步锁的优化⽅法。
思考题
请问以下Synchronized同步锁对普通⽅法和静态⽅法的修饰有什么区别?
// 修饰普通⽅法
public synchronized void method1() {
// code
}
// 修饰静态⽅法
public synchronized static void method2() {
// code
}
期待在留⾔区看到你的答案。也欢迎你点击“请朋友读”,把今天的内容分享给身边的朋友,邀请他⼀起学习。
精选留⾔
bro.
Synchronized锁升级步骤
- 偏向锁:JDK6中引⼊的⼀项锁优化,它的⽬的是消除数据在⽆竞争情况下的同步原语,进⼀步提⾼程序的运⾏性能 ,
- 偏向锁会偏向于第⼀个获得它的线程,如果在接下来的执⾏过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。⼤多数情况下,锁不仅不存在多线程竞争,⽽且总是由同⼀线程多次获得,为了让线程获得锁的代价更低⽽引
⼊了偏向锁
- 当锁对象第⼀次被线程获取的时候,线程使⽤CAS操作把这个锁的线程ID记录再对象Mark Word之中,同时置偏向标志位1
。以后该线程在进⼊和退出同步块时不需要进⾏CAS操作来加锁和解锁,只需要简单地测试⼀下对象头的Mark Word⾥是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。
- 如果线程使⽤CAS操作时失败则表示该锁对象上存在竞争并且这个时候另外⼀个线程获得偏向锁的所有权。当到达全局安全点(safepoint,这个时间点上没有正在执⾏的字节码)时获得偏向锁的线程被挂起,膨胀为轻量级锁(涉及Monitor
Record,Lock Record相关操作,这⾥不展开),同时被撤销偏向锁的线程继续往下执⾏同步代码。
- 当有另外⼀个线程去尝试获取这个锁时,偏向模式就宣告结束
- 线程在执⾏同步块之前,JVM会先在当前线程的栈帧中创建⽤于存储锁记录(Lock Record)的空间,并将对象头中的Mard Wo
rd复制到锁记录中,官⽅称为Displaced Mark Word。然后线程尝试使⽤CAS将对象头中的Mark Word替换为指向锁记录的指针
。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使⽤⾃旋来获取锁。如果⾃旋失败则锁会膨胀成重量级锁。如果⾃旋成功则依然处于轻量级锁的状态
- 轻量级锁的解锁过程也是通过CAS操作来进⾏的,如果对象的Mark Word仍然指向线程的锁记录,那就⽤CAS操作把对象当前的Mark Word和线程中赋值的Displaced Mark Word替换回来,如果替换成功,整个同步过程就完成了,如果替换失败,就说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程
- 轻量级锁提升程序同步性能的依据是:对于绝⼤部分的锁,在整个同步周期内都是不存在竞争的(区别于偏向锁)。这是⼀个经验数据。如果没有竞争,轻量级锁使⽤CAS操作避免了使⽤互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发⽣了CAS操作,因此在有竞争的情况下,轻量级锁⽐传统的重量级锁更慢
简单概括为:
- 检测Mark Word⾥⾯是不是当前线程ID,如果是,表示当前线程处于偏向锁
- 如果不是,则使⽤CAS将当前线程ID替换到Mark Word,如果成功则表示当前线程获得偏向锁,设置偏向标志位1
- 如果失败,则说明发⽣了竞争,撤销偏向锁,升级为轻量级锁
- 当前线程使⽤CAS将对象头的mark Word锁标记位替换为锁记录指针,如果成功,当前线程获得锁
- 如果失败,表示其他线程竞争锁,当前线程尝试通过⾃旋获取锁 for(;;)
- 如果⾃旋成功则依然处于轻量级状态
- 如果⾃旋失败,升级为重量级锁
- 索指针:在当前线程的栈帧中划出⼀块空间,作为该锁的锁记录,并且将锁对象的标记字段复制到改锁记录中!
2019-06-18 17:15
作者回复
赞
2019-06-19 09:36
-W.LI-
⽼师好!获取偏斜锁和轻量级锁的时候使⽤的CAS操作预期值传的是null(希望锁已释放),替换后值是当前线程什么?
2019-06-18 08:20
作者回复
⽼师没有看懂你问的具体问题,麻烦再描述⼀下你的问题。
2019-06-18 09:53
晓杰
感觉讲得有点晦涩啊,不知道其他⼈什么感觉
2019-06-16 20:47
作者回复
如果哪⾥不懂的,可以多提问,希望我能帮助到你。
2019-06-17 10:06
苏志辉
entrylist和waitset那个地⽅不太理解,monitorenter失败后会进⼊entrylist吧,只有调⽤wait⽅法才会进⼊waitset吧,还请⽼师指点下
2019-06-16 18:05
作者回复
在获取到参与锁资源竞争的线程会进⼊entrylist,线程monitorenter失败后会进⼊到waitset,此时说明已经有线程获取到锁了, 所以需要进⼊等待。调⽤wait⽅法也会进⼊到waitset。
2019-06-17 10:05
nightmare
加在普通⽅法锁对象是当前对象,其ObjectMonitor就是对象的,⽽静态⽅法上,锁对象就是字节码对象,静态⽅法是所有对象 共享的,锁粒度⽐较⼤
2019-06-15 09:05
Jxin
1.8后,可以尽量采⽤并发包中的⽆锁或则称乐观锁来实现。读写极端场景可以看情况选⽤读写锁或票据锁。
课后题,前者锁实例,后者锁类的字节码对象。后者⼒度太⼤应该结合业务场景尽量规避。
2019-06-15 13:41
陆离
⾮静态⽅法是对象锁,静态⽅法是类锁
2019-06-15 08:31
任鹏斌
普通⽅法中锁的是当前对象,静态⽅法锁的是静态类
2019-07-03 21:12
作者回复
对的,普通⽅法中的锁时锁对象,⽽修饰静态⽅法是类锁
2019-07-04 10:49
chris~jiang
⽼师,您好,synchronized锁只会升级,不会降级吧?如果系统只在某段时间⾼并发,升级到了重量级锁,然后系统变成低并发了,就⼀直是重量级锁了吗?请⽼师解惑,谢谢
2019-07-02 09:32
不靠谱~
1.课后作业:实际对象锁和类对象锁的区别,锁对象不⼀样。
2. 1.8后CurrentHashmap已经不⽤segment策略了,想请教⼀下⽼师1.8后是怎样保证性能的呢?
3.对锁升级不太了解的同学可以看⼀下《Java并发编程的艺术》。⾥⾯有很详细的介绍,不过也是⽐较难理解,多看⼏遍。
2019-06-20 08:25
作者回复
JDK1.8之后ConcurrentHashMap就放弃了分段锁策略,⽽是直接使⽤CAS+Synchronized⽅式保证性能,这⾥的锁是指锁table 的⾸个Node节点。在添加数据的时候,如果Node数组没有值的情况,则会使⽤CAS添加数据,CAS成功则添加成功,失败则进⼊锁代码块执⾏插⼊链表或红⿊树或转红⿊树操作。
2019-06-20 09:32
bro.
修饰普通⽅法是改类的对象,⽐如class A 创建了两个对象 class A1 跟class A2,对于method1来说A1,A2直接不是互斥的,但是对于静态⽅法或者synchronized(A.class)表示加锁对象为.class⽂件,⼀个项⽬只有唯⼀⼀个class⽂件,所以是互斥的
2019-06-18 17:05
⿊崽
锁升级的图中显示markword是否存储线程ID的图中,两个路径是与不是,是不是画反了?是的话,要cas替换成⾃⼰。不是, 那么就直接获取偏向锁
2019-06-17 08:16
作者回复
是的,感谢⿊崽同学的提醒。
2019-06-18 09:29
拉可⾥啦
在⾼并发的场景下,只能使⽤重量锁了吧 因为锁最终会升级到重量级锁,那么就需要提前关闭偏向锁和⾃旋锁
2019-06-30 11:53
作者回复
对的
2019-07-02 09:58
拉可⾥啦
⽼师你好,在偏向锁中,如果另⼀个线程通过cas获取到了偏向锁,那么之前获取到的偏向锁的线程还在执⾏中,是否被中断了,还是执⾏完
2019-06-29 15:18
作者回复
这个时候是⽆法获取到偏向锁的
2019-06-30 12:07
拉可⾥啦
在使⽤偏向锁中,进⾏cas替换失败的原因是什么?我认为有两点:1.被替换的线程⽬前还在执⾏中 2.被替换的线程已执⾏完毕,但是有其它线程同时获取到了偏向锁。 不知我理解的是否合理,还请⽼师指点。
2019-06-28 11:29
作者回复
对的,理解正确
2019-06-30 10:37
拉可⾥啦
有个偏向锁的疑问:偏向锁认为⾃始⾄终只有⼀个线程访问,那么是怎么确定请求过来的是同⼀个线程呢?因为每次请求线程都是不同的线程ID啊
2019-06-28 10:28
作者回复
同⼀个线程,线程ID是⼀样的,是根据线程ID区别的。
2019-06-28 12:03
Geek_ebda96
因此,在⾼并发场景下,当⼤量线程同时竞争同⼀个锁资源时,偏向锁就会被撤销,发⽣ stop the word 后, 开启偏向锁⽆疑会带来更⼤的性能开销,⽼师,这句话的意思是说撤销偏向锁的过程,发⽣stoop the word⾮常耗时影响性能吗?偏向锁不适合⾼并发场景,但低并发但场景,⽤偏向锁的意义在哪⾥呢?低并发关闭偏向锁,直接获取轻量级的锁,这个也没啥问题吧
2019-06-26 23:16
作者回复
是的,理解没问题。偏向锁适合当个永久线程下或有嵌套锁的资源锁操作。
2019-06-28 11:44
Better me
⽼师能否解释下CurrentHashmap分段锁的那张图,不是很了解分段锁的实现
2019-06-24 22:12
作者回复
最开始CurrentHashmap只有⼀个table[]数组,如果要操作该CurrentHashmap,则需要锁住table[]对象,这个时候所有的操作都来竞争⼀个table[]对象锁。
⽽分段锁则是将table[]数组分解称为了⼀个Segment[]数组,每个Segment[]的元素⾥⾯包含了⼀个HashEntry
2019-06-26 10:36
VIC
怎么让synchronized使⽤偏向锁,轻量级锁呢?
2019-06-24 21:11
作者回复
JVM默认情况下会使⽤偏向锁和轻量级锁,只有在竞争锁资源⾮常激烈的情况下,才会升级到重量级锁。
所以我们可以减少锁竞争来保持synchronized使⽤偏向锁,轻量级锁。
2019-06-26 10:17
左瞳
还没看完,看到了图我反⼿⼀个赞
2019-06-23 12:01