文章目录
- 为什么synchronized性能低下?
- JDK1.6对synchronized的优化
- 偏向锁(无锁状态)
- 轻量级锁(自旋)
- 重量级锁(阻塞)
- 这些锁的优缺点
早期JDK对synchronized的实现是重量级的,每一次的获取锁都需要请求OS。
而在大部分情况下,同步方法是运行在单线程环境下的,如果每次都调用OS去为我们加锁,那么将严重影响性能。
在JDK1.6中对锁进行了升级。
比如锁粗化,轻量级锁,偏向锁,适应性自旋等技术来减少锁操作的开销。
第一次的时候是一个偏向锁,此时是没有线程争用的情况,只是记录线程ID,此时是没有加锁的。如果有争用情况,那么就要升级为自旋锁,自旋十次,此时如果还没有得到,那么就锁升级为重量锁。
为什么synchronized性能低下?
我们使用的Lock互斥锁,其实底层是由操作系统的mutex lock提供的,而我们编写的临界区代码运行在JVM上,而JVM其实是运行在操作系统上的一个用户程序,而如果想要使用操作系统的mutex lock,那么就需要向操作系统进行申请,此时就会发生从用户态到内核态的切换,这是非常消耗系统资源和性能的。因此如果JDK还继续使用这样的锁自然性能是很低的。而在JDK1.6对synchronized进行优化之后,synchronized的性能提高了很多。
JDK1.6对synchronized的优化
JDK1.6对synchronized的优化,他将锁分为了三种
偏向锁->轻量级锁->重量级锁(Monitor)
偏向锁(无锁状态)
偏向锁基于CAS实现,他其实是一种无锁状态。为什么呢?
因为偏向锁的条件发生再只有一个线程去访问临界区的时候,此时锁直接被当前线程锁持有,并且设定这个线程的对象头信息中的属性,设定当前锁为当前线程所持有。
之后只要还是这个线程访问临界区,那么只需要比较头信息中的属性是否相等,如果相等则直接获取这把锁。就好像一个人有一个车位,他在车位上面写了纸条设置为小张可用,那么如果它人离开了一会又回来发现,此时车位上面还是写着小张可用,那么他就直接停车进去就行了。所以此时很明显,压根就没有发生竞争,所以此时就是一个无锁状态,也就是偏向锁,此时的锁偏向了某一个线程。
轻量级锁(自旋)
但是不可能一直线程访问临界区把,总不能只有一个线程访问把。所以此时对应的场景就是,有一个小李也要用车位,但是车位其实是公共的呀,总不能你写个条子就给你了把,但是此时小张还停车在里面,所以他就催小张离开,但是小张也不会马上就能离开呀,所以他就需要进行等待,他会每隔一定时间就催一下小张。此时就是自旋锁。这与CAS还是有区别的,CAS一般是一直询问,而自旋锁是隔一段时间问一次。如果好像这是这样子,那好像没有必要升级为重量级锁吧,就让他们等着呗,然后偶尔问一问就好了呀。
重量级锁(阻塞)
但是很明显,如果每一个人都催,那其实停车位的人是很急的,也消耗性能对不。
所以此时如果并发情况严重,那么锁会升级为重量级锁,也就是大家也不催你了,就干等,因为催你我也催不动呀,你也不可能马上就挪车。但是如果车位空出来了,那么其他线程马上就会知道,然后开始进行竞争,谁先抢到就是谁的。(非公平)
所以轻量级锁和重量级锁的区别就在于,是否自旋,是否询问锁的状态。
很明显上面的锁,没有公平,先来的人也未必抢得到,所以就有一个公平锁,那就需要使用队列,先来先得。
这些锁的优缺点
偏向锁:
偏向锁优点在于加锁和解锁不需要额外的消耗,和执行非同步方法基本没什么时间上的差距。
缺点在于如果线程间存在竞争,会带来额外的锁撤消的消耗,因此适用于只有一个线程访问同步代码块的情况。
轻量级锁:
优点在于竟争的线程不会阻塞,提高了程序的响应。
缺点在于如果始终得不到锁的竞争线程,会使用自旋,消耗CPU资源,
因此适用于追求响应时间的场景。
重量级锁:
优点在于钱程竞争不使用自旋,不会消耗CPU。
缺点在于线程阻塞,响应时间缓慢,并且由于是不公平的,可能出现线程饿死或者超时现象。因此适用于追求吞吐量。