阿华代码,不是逆风,就是我疯,
你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!
目录
一:乐观锁和悲观锁
1:乐观锁
2:悲观锁
3:总结
二:轻量级锁和重量级锁
1:轻量级锁
2:重量级锁
3:总结
三:自旋锁和挂起等待锁
1:自旋锁
2:挂起等待锁
四:普通互斥锁和读写锁
1:普通互斥锁
2:读写锁
(0)知识联系
(1)此处解释
(2)总结
3:为什么引入读写锁
(1)场景一
(2)场景二
(3)优化
五:公平锁和非公平锁
1:两者对比
六:可重入锁和不可重入锁
七:synchronized自适应阶段
1:偏向锁阶段
2:轻量级锁阶段
3:重量级锁阶段
4:总结
八:锁消除
九:锁粗化
1:“粒度”
2:定义
一:乐观锁和悲观锁
1:乐观锁
在加锁过程中,预估发生锁冲突的概率小,降低加锁的工作量,加锁的效率就提高了,安全系数不高(可能会引发占用大量cpu资源的问题)
2:悲观锁
在加锁过程中,预估发生锁冲突的概率大,提升加锁的工作量,加锁的效率就下降了,但是安全系数高
3:总结
乐观锁——牺牲安全性换来效率
悲观锁——牺牲效率换来安全性
二:轻量级锁和重量级锁
引入:在乐悲观的基础上延伸出来的
1:轻量级锁
加锁的开销小,速度快——一般指乐观锁
2:重量级锁
加锁的开销大,速度慢——一般指悲观锁
3:总结
乐/悲观锁是加锁前对没有发生的事情的预估
轻/重量锁是加锁后对结果的评价
整体上来说,两者都是在对一件事情进行描述
三:自旋锁和挂起等待锁
1:自旋锁
自旋锁是轻量级锁的一种实现,也是乐观锁,通过与一个while循环搭配,如果获取到锁,那就结束循环;如果没有获取到锁,不会阻塞放弃cpu,而是继续下一次循环,直到获取到锁
(1)使用场景:锁冲突不激烈
(2)优点:其它线程一旦释放锁,就能快速获取到锁
(3)缺点:会占用消耗大量的cpu资源
2:挂起等待锁
挂起等待锁是重量级锁的一种实现,也是悲观锁,锁释放后并不能第一时间获取到锁,而是要通过操作系统的内核进行调度去获取锁,这个等待的过程时间较长。
(1)使用场景:锁冲突激烈
(2)优点:在内核调度的等待时间中,cpu可以做别的事情,即降低了cpu的资源消耗
(3)缺点:不能第一时间获取到锁
四:普通互斥锁和读写锁
1:普通互斥锁
与synchronized相似,可以进行加锁和解锁
2:读写锁
(0)知识联系
想想之前文章写到的MySQL事务处理——三读
“脏读”——给写加锁(写的时候不能读)
“不可重复读”——给读加锁(读的时候不能写)
“幻读”——读写都加上锁
(1)此处解释
读锁和读锁之间,不会发生锁冲突(不会阻塞)
写锁和写锁之间,会发生锁冲突(会阻塞)
读锁和写锁之间,会发生锁冲突(会阻塞)
(2)总结
一个线程加读锁的时候,另一个线程只能读,不能写
一个线程加写锁的时候,另一个线程只能写,不能读
3:为什么引入读写锁
(1)场景一
两个线程一起读,本身就是线程安全的不互斥,如果使用synchronized,就让两者互斥了
(2)场景二
如果一个线程读,一个线程写,也是不可以的(参考MySQL事务那一章节)
(3)优化
在实际开发中,本身读操作就是非常频繁的,引入读写锁可以大大节省下来“并发读”带来的锁冲突的资源消耗,
五:公平锁和非公平锁
1:两者对比
我们知道线程遵守“随机调度”的原则,所以在在加锁过程中就产生了“锁竞争”这一现象,在“Java”中规定公平就是遵守“先来后到”这一原则,synchronized本身就是非公平锁——一旦解锁,下一个加锁的线程是无法确定的。
所以我们引入队列,记录每个线程的顺序,依次加锁,实现“公平锁”
六:可重入锁和不可重入锁
一个线程对同一个对象加了两次锁,不会产生死锁,根据{}和内置的计数器来确定真正加锁和解锁的位置,synchronized就是可重入锁(推荐看看阿华之前写的那篇《多重入锁》文章哈)
系统自带的锁一般是不可重入锁。
七:synchronized自适应阶段
IDEA中提供的synchronized加锁具有自适应能力,内部会自动评估当前锁冲突的激烈程度,在乐观锁(系列:乐观锁,轻量级锁,自旋锁)和悲观锁(系列:悲观锁,重量级锁,挂起等待锁)中进行筛选。
这也是一种优化方式
1:偏向锁阶段
假设现在有一把偏向锁,A线程拿上了这把锁,不是真正意义上的加锁(假加锁),而是让这把锁对A线程有一个(轻量)标记,如果有其他的线程竞争也想要拿上这把锁,那A就会先一步加锁(真加锁)。
理解:
偏向锁标记是对象里头的一个属性,每个锁对象都有这么一个标记,锁对象首次被加锁都会先进入偏向锁阶段
如果没有锁竞争,那么下次加锁,还是会进入偏向锁阶段。
如果在加锁的过程中遭遇“锁竞争”,那么就会升级为“轻量级锁”阶段,
优点:非必要不加锁,没有锁竞争,偏向锁能大大提高效率
彩蛋:锁先生舔着女神A,女神A吊着锁先生就是不跟他确认关系,忽然有个姑娘B加入进来,想和锁先生确认关系,女神A就慌了~,赶快跟锁先生就确认男女朋友了,像极了爱情~~~
2:轻量级锁阶段
通过“自旋锁”的方式实现,synchronized内部也会统计有多少个线程在“锁竞争”,因为一旦超过某一个线程数量限制,大量的自旋锁会非常消耗cpu资源,此时就会升级为“重量级锁阶段”
优点:其他线程一旦释放锁,就能快速拿到锁
缺点:非常消耗cpu资源
3:重量级锁阶段
承接上文,此时线程放弃自旋锁,进入“阻塞等待”,当解锁后,系统在随机唤醒线程进行加锁。
4:总结
以上synchronized加锁的三个阶段是层层递进升级的,在目前Java中是不能够降级的,
八:锁消除
假如一个线程加了锁,但是一眼看过去这个线程肯定没有线程安全问题,那么在编译器编译代码的时候就会自动“消除锁”,提高效率。
针对一些模棱两可的代码,编译器不知道要不要加锁,统一都不会进行锁消除
九:锁粗化
1:“粒度”
synchronized{}花括号中的代码数量越少,则称锁的粒度越细;代码越多,锁的粒度越粗
2:定义
把多个细粒度的锁,合并成一个粗粒度的锁,就叫锁粗化
锁粗化也是为了提高效率,它跟可重入锁可不一样哦