共计 2680 个字符,预计需要花费 7 分钟才能阅读完成。
作为一个 Java 开发多年的人来说,必定多多少少相熟一些锁,或者听过一些锁。明天就来做一个锁相干总结。
须要高清图,进入公众号分割我
乐观锁和乐观锁
乐观锁
顾名思义,他就是很乐观,把事件都想的最坏,是指该锁只能被一个线程锁持有,如果 A 线程获取到锁了,这时候线程 B 想获取锁只能排队期待线程 A 开释。
在数据库中这样操作:
select user_name,user_pwd from t_user for update;
乐观锁
顾名思义,乐观,人乐观就是什么事都想得开,闯到桥头天然直。乐观锁就是我都感觉他们都没有拿到锁,只有我拿到锁了,最初再去问问这个锁真的是我获取的吗?是就把事件给干了。
典型的代表:CAS
=Compare and Swap
先比拟哈,资源是不是我之前看到的那个,是那我就把他换成我的。不是就算了。
在 Java 中 java.util.concurrent.atomic
包上面的原子变量就是应用了乐观锁的一种实现形式 CAS
实现。
通常都是 应用 version、工夫戳等来比拟是否已被其余线程批改过。
update t_user set name="Java 后端技术全栈" where t_version=1
应用乐观锁还是应用乐观锁?
在乐观锁与乐观锁的抉择下面,次要看下两者的区别以及实用场景就能够了。
响应效率
如果须要十分高的响应速度,倡议采纳乐观锁计划,胜利就执行,不胜利就失败,不须要期待其余并发去开释锁。乐观锁并未真正加锁,效率高。一旦锁的粒度把握不好,更新失败的概率就会比拟高,容易产生业务失败。
抵触频率
如果抵触频率十分高,倡议采纳乐观锁,保障成功率。抵触频率大,抉择乐观锁会须要多次重试能力胜利,代价比拟大。
重试代价
如果重试代价大,倡议采纳乐观锁。乐观锁依赖数据库锁,效率低。更新失败的概率比拟低。
乐观锁如果有人在你之前更新了,你的更新该当是被回绝的,能够让用户从新操作。乐观锁则会期待前一个更新实现。这也是区别。
偏心锁和非偏心锁
偏心锁
顾名思义,是偏心的,先来先得,FIFO;必须恪守排队规定。不能僭越。多个线程依照申请锁的程序去取得锁,线程会间接进入队列去排队,永远都是队列的第一位能力失去锁。
在 ReentrantLock
中默认应用的非偏心锁,然而能够在构建 ReentrantLock
实例时候指定为偏心锁。
ReentrantLock fairSyncLock = new ReentrantLock(true);
假如线程 A 曾经持有了锁,这时候线程 B 申请该锁将会被挂起,当线程 A 开释锁后,如果以后有线程 C 也须要获取该锁,那么在偏心锁模式下,获取锁和开释锁的步骤为:
- 线程 A 获取锁 —> 线程 A 开释锁
- 线程 B 获取锁 —> 线程 B 开释锁;
- 线程 C 获取锁 —> 线程开释锁;
长处
所有的线程都能失去资源,不会饿死在队列中。
毛病
吞吐量会降落很多,队列外面除了第一个线程,其余的线程都会阻塞,CPU 唤醒阻塞线程的开销会很大。
非偏心锁
顾名思义,老子才不论你们谁先排队的,也就是平时大家在生活中很厌恶的。生存中排队的很多,上车排队、坐电梯排队、超市结账付款排队等等。然而不是每个人都会遵守规则站着排队,这就对站着排队的人来说就不偏心了。等抢不到后再去乖乖排队。
多个线程去获取锁的时候,会间接去尝试获取,获取不到,再去进入期待队列,如果能获取到,就间接获取到锁。
下面说过在 ReentrantLock
中默认应用的非偏心锁,两种形式
ReentrantLock fairSyncLock = new ReentrantLock(false);
或者
ReentrantLock fairSyncLock = new ReentrantLock();
都能够实现非偏心锁。
长处
能够缩小 CPU 唤醒线程的开销,整体的吞吐效率会高点,CPU 也不用去唤醒所有线程,会缩小唤起线程的数量。
毛病
大家可能也发现了,这样可能导致队列两头的线程始终获取不到锁或者长时间获取不到锁,导致饿死。
独享锁和共享锁
独享锁
独享锁也叫排他锁 / 互斥锁,是指该锁一次只能被一个线程锁持有。如果线程 T 对数据 A 加上排他锁后,则其余线程不能再对 A 加任何类型的锁。取得排他锁的线程既能读数据又能批改数据。JDK
中的 synchronized 和 JUC
中 Lock 的实现类就是互斥锁。
共享锁
共享锁是指该锁可被多个线程所持有。如果线程 T 对数据 A 加上共享锁后,则其余线程只能对 A 再加共享锁,不能加排他锁。取得共享锁的线程只能读数据,不能批改数据。
对于 ReentrantLock
而言,其是独享锁。然而对于 Lock 的另一个实现类ReadWriteLock
,其读锁是共享锁,其写锁是独享锁。
- 读锁的共享锁可保障并发读是十分高效的,读写,写读,写写的过程是互斥的。
- 独享锁与共享锁也是通过
AQS
来实现的,通过实现不同的办法,来实现独享或者共享。
可重入锁
若以后线程执行中曾经获取了锁,如果再次获取该锁时,就会获取不到被阻塞。
public class RentrantLockDemo {public synchronized void test(){System.out.println("test");
}
public synchronized void test1(){System.out.println("test1");
test();}
public static void main(String[] args) {RentrantLockDemo rentrantLockDemo = new RentrantLockDemo();
// 线程 1
new Thread(() -> rentrantLockDemo.test1()).start();}
}
当一个线程执行 test1()
办法的时候,须要获取 rentrantLockDemo
的对象锁,在 test1
办法汇总又会调用 test 办法,然而 test()的调用是须要获取对象锁的。
可重入锁也叫 递归锁,指的是同一线程外层函数取得锁之后,内层递归函数依然有获取该锁的代码,但不受影响。
对于锁是如何实现可重入的,请参考后面的文章。
举荐一起看一下文章
疾速把握并发编程 — 深刻学习 Condition
疾速把握并发编程 — 细说 ReentrantLock 和 AQS
疾速把握并发编程 —synchronized 篇(上)
疾速把握并发编程 —synchronized 篇(下)
老铁,点个 在看 是最大的激励