Java中的锁有很多种,经常会听到“锁”这个词。
犹如每天出门时,🔑就是一种“锁”,拿不到🔑,就进不去了。
锁的种类
1 悲观 Vs 乐观
林妹妹比较悲观,宝玉比较乐观
悲观如林妹妹,怕多行一步路,怕多说一句话,被人耻笑了去。担心外人所说,“金石良缘”,怕宝玉的心思会变。不敢多表露心思,总是猜疑,总想去证明。
而宝玉,天性乐观一些,总觉得这个妹妹是见过的,总觉得以后就是会和这个妹妹在一起的,不需要多证明。“你证我证,心证意证。是无有证,斯可云证。无可云证,是立足境”。
1.1 悲观锁
看名字便知,它是悲观的,总是想到最坏的情况。
锁也会悲观,它并不是难过,它只是很谨慎,怕做错。
每次要读data的时候,总是觉得有“坏人”会修改数据,所以先加个🔐,让“坏人”不能改数据,再慢慢读~
就像你每次出门用🔑锁门一样。
应用:
synchronized关键字和Lock的实现类都是悲观锁。
1.2 乐观锁
它很乐观,总是想着最好的情况。
它比较大条,不会太担心。如果要发生,总会发生,如果不会发生,那就不会。为什么要担心那么多?
每次读data时,总是乐观地想没有“坏人”会同时修改数据,不用加锁,放心地读data。
但在更新的时候会判断一下在此期间别人有没有去更新这个数据。
相信“天下无贼”。
事事无绝对,悲观也好乐观也好,没有绝对的悲观,也没有绝对的乐观。只是在这个当时,相信,还是不相信。
悲观锁 Vs 乐观锁
类型 | 实现 | 使用场景 | 缺点 |
---|---|---|---|
悲观锁 | synchronized关键字和Lock的实现类 | 适合写操作多的场景,可以保证写操作时数据正确 | 如果该事务执行时间很长,影响系统的吞吐量 |
乐观锁 | 无锁编程,CAS算法 | 适合读操作多的场景,能够大幅提升其读操作的性能 | 如果有外来事务插入,那么就可能发生错误 |
乐观锁之CAS(Compare and Swap 比较并交换)
是乐观锁的一种实现方式。
简单来说,有3个三个操作数:
- 需要读写的内存值 V。
- 进行比较的值 A。
- 要写入的新值 B。
2 公平 Vs 非公平
没有绝对的公平,也没有绝对的不公平。
公平,就是按顺序排队嘛。
公平锁维护了一个队列。要获取锁的线程来了都排队。
非公平,上来就想抢到锁,好像一个不讲道理的,抢不到的话,只好再去乖乖排队了。
非公平锁没有维护队列的开销,没有上下文切换的开销,可能导致不公平,但是性能比fair的好很多。看这个性能是对谁有利了。
3 独享 Vs 共享
好比你自己的自行车,就是你独享的资源。
OFO、摩拜单车,就是共享的资源,但也是一次只能一个人用啦。更好的例子,比如说一条小溪,可以同时好几只小鹿在溪边喝水小溪就是共享的嘛~
4 可重入锁 Vs 非可重入锁
广义上的可重入锁,而不是单指JAVA下的ReentrantLock。
可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。
这句话神马意思?
这种锁是可以反复进入的。
当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
class MyClass {
public synchronized void method1() {
enterNextRoom();
}
public synchronized void method2() {
// todo
}
}
两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
可重入锁最大的作用是避免死锁。
- synchronized
- ReentrantLock
构造函数提供了是否公平锁的初始化
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
使用ReentrantLock必须在finally控制块中进行解锁操作。
在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍,而ReentrantLock确还能维持常态。
高并发量情况下使用ReentrantLock。
优点:
可一定程度避免死锁。
- Semaphore
- AtomicInteger、AtomicLong等
参考:
- 关于Java锁机制面试官会怎么问,深刻易懂 https://mp.weixin.qq.com/s/NG_xGcEHsxOJbgBgHRTK4Q
- 不可不说的Java“锁”事
https://mp.weixin.qq.com/s/E2fOUHOabm10k_EVugX08g