类别 | synchronized | Lock |
---|---|---|
存在层次 | Java的关键字,在jvm层面上 | Lock是一个接口而已 |
锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 程序在finally释放锁 |
锁的获取 | 假设a获得锁,b线程在等待,如果a阻塞,b会一直阻塞等待 | 分情况而定,Lock有很多种获取锁的方式,大致可以获得锁,但是线程可以自己中断不等 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入,不可中断,非公平 | 可重入,可以判断,可以公平,可以非公平 |
性能 | 少量同步,如果太多线程并发,性能不如Lock | 适合并发高点的大量同步 |
为什么大量同步synchronized会性能这么低?
据我所知,synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。
现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
有人做过测试
关于使用场景:
主要是根据上面表格里描述的两者的不同,比如并发量高点的选择lock,如果需要实现非公平的,可以synchronized,也可以lock
synchronized源码简单描述
我们都知道synchronized底层是通过monitor来实现同步
在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)。
ObjectMonitor中有几个关键属性:
_owner:指向持有ObjectMonitor对象的线程
_WaitSet:存放处于wait状态的线程队列
_EntryList:存放处于等待锁block状态的线程队列
_recursions:锁的重入次数
_count:用来记录该线程获取锁的次数
- 线程T等待对象锁:_EntryList中加入T。
- 线程T获取对象锁:_EntryList移除T,_owner置为T,计数器_count加1。
- 线程T中锁对象调用wait():_owner置为null,计数器_count减1,_WaitSet中加入T等待被唤醒。
- 持有对象锁的线程T执行完毕:复位变量的值,以便其他线程进入获取monitor。
参考:这几篇讲synchronized原理都讲得挺好的,就是我现在好像还没消化掉… 先保存日后再看
【原创】Java并发编程系列07 | synchronized原理
【原创】Java并发编程系列08 | synchronized锁优化
【死磕Java并发】-----深入分析synchronized的实现原理