Java中的锁可以分为隐式锁和显示锁,Lock接口的锁都是显示锁。JVM内置锁就是隐式锁,synchronized就是隐式的锁。
显示锁:需要手动释放锁,可以设置是否为公平锁
隐式锁:不需要手动释放锁,非公平锁
Monitor
Lock接口实现的锁底层是通过AQS同步队列实现的。用到了unsafe.park()方法。synchronized 底层有一个monitor监视器,会监控持有锁的对象。如下图:
monitorenter表示当前程序将进入同步块monitorexit表示即将退出同步块,并且释放锁
那么JVM怎么知道我当前的对象是否已经加锁了呢。
synchronized (object) { //代码逻辑}
如上图,Monitor调用Enter方法进入监视区,它会监视object对象里面是否有锁标记,如果没有就给object加上锁标记,并执行后面的逻辑,最后释放锁,取消object的锁标记。Monitor.Exit退出监视区,并释放锁。
锁Monitor.Enter进入后,发现对象object上面已经有锁标记了,那么返回Monitor.Enter失败,并退出Monitor。之后再循环重试。
对象头
锁标记保存在对象头(方法区的类信息)中的Mark Word中。
锁升级
由于JDK的优化,synchronized锁有一个升级,极大的提升了锁的性能。
锁对象刚创建时,对象头里面是无锁的状态,当第一个线程进来时。锁升级为偏向锁;当第二个线程进来,升级为轻量级锁;第三个线程进来,等待抢锁,第四个,第五个线程进来也是等待争抢锁。争抢的线程变多了,锁就会升级为重量级锁。
无锁
无锁态时 Mark Word 标记位为01,是否偏向标记为0。
偏向锁
此时是否偏向标记为1。
轻量级锁
锁标志位
重量级锁
升级为重量级锁时,线程会有从用户态到内核态的切换,所以说,大量线程抢锁时,性能不是很好,建议使用Lock接口实现的锁。
注意,以上两种锁都是单机的锁,现在的系统都是分布式的,需要分布式锁。可以用zookeeper或redis实现。市面上已经有成熟的分布式锁框架。像Redisson,Curator等都很不错。
Lock 与synchronized
(1)synchronized不会导致死锁现象发生;而Lock可能造成死锁现象Lock可以让等待。
(2)锁的线程响应中断,而synchronized却不行。
(3)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到 。
(4)Lock可以提高多个线程进行读操作的效率 。
(5)性能上,竞争不激烈两者差不多;非常激烈时(即有大量线程同时竞争) Lock远远优于synchronized。所以说,在具体使用时要根据适当情况选择。