锁分类 : 互斥锁、自旋锁、读写锁、乐观锁、悲观锁
互斥锁: 操作系统层面的重量级锁、会带来上下文切换的开销、锁的数量有限;如果加锁失败,操作系统会将线程阻塞
自旋锁: CAS(Compare And Swap) 、会一直循环去加锁、但是如果一直获取不到锁、cpu开销会很大;
- 互斥锁加锁失败后,线程会释放 CPU ,给其他线程;
- 自旋锁加锁失败后,线程会忙等待,直到它拿到锁;
读写锁:读共享和写独占
当没有写操作时、多线程已经并发读取、提高读的性能;一旦有写操作,读和写操作都会阻塞、防止共享资源的不一致问题;
在此基础上还有读优先和写优先、不过只适合特定的场景
乐观锁和悲观锁
乐观锁:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。
放弃后如何重试,这跟业务场景息息相关,虽然重试的成本很高,但是冲突的概率足够低的话,还是可以接受的。
悲观锁:多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁
应用场景
常见的 SVN 和 Git (以及在线编辑文档)也是用了乐观锁的思想,先让用户编辑代码,然后提交的时候,通过版本号来判断是否产生了冲突,发生了冲突的地方,需要我们自己修改后,再重新提交。
乐观锁虽然去除了加锁解锁的操作,但是一旦发生冲突,重试的成本非常高,所以只有在冲突概率非常低,且加锁成本非常高的场景时,才考虑使用乐观锁。
Java锁:Synchronized和Lock
Synchronized是java的关键字,用来做并发控制: 字节码层面 monitorenter moniterexit
jdk1.6以后的优化 、Synchronized使用锁的变化过程 :偏向锁(无锁) --->自旋锁(cas)---->互斥锁
单个线程竞争,打个标签就行 偏向锁;多线程竞争,开始用cas去竞争;cas到达一定次数、使用操作系统互斥量(重量级锁、无法撤销)
可以看到Synchronized适合竞争不那么激烈的并发场景下;
java的java.util.concurrent.locks包,包含lock相关的实现类、可重入锁、读写锁、公平锁/非公平锁
底层使用cas和AQS来实现以及操作系统的阻塞方法,通过cas和AQS提供灵活操作的java层API;
没有重量级锁,但是也没有Synchronized的自动加锁自动撤销,所以相对适合并发激烈的情况下,灵活的使用API会提高并发的性能。
Mysql锁:表锁、行锁、间隙锁
我觉得mysql的锁本质上锁分段、这个概念在java中也有。
ConcurrentHashMap 在jdk1.7中讲数组分为16段、不同段使用不同锁。
在JDK1.8中将ConcurrentHashMap通过同步关键字Synchronized锁链表或者红黑树。
本质上和Mysql的表级锁和行级锁非常像。
表锁:实现简单、不会出现死锁,发生锁冲突几率高,并发低。
行锁:实现复杂、会出现死锁,发生锁冲突几率低,并发高。 注意Mysql的innoDB才支持行级锁
Mysql涉及到插入和查询、也有读写锁以及读写意向锁
Mysql的间隙锁(Next-Key) : 锁住一行记录和下一行记录的间隙 ,例如 [1,2} [1,5}
行级锁的实现通过索引实现,索引间也是通过行级锁实现、如果没有索引按照我测试的结果、会去找主键索引、如果范围太多可能会锁表。
参考
https://blog.csdn.net/qq_34827674/article/details/108608566 互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景
https://www.cnblogs.com/ljl150/p/12514198.html synchronized实现原理及其优化-(自旋锁,偏向锁,轻量锁,重量锁)