目录
- 总结*synchronized*
- *初识synchronized*
- 使用synchronized
- synchronized的特性
- (1)可重入性
- (2)自适应过程
- (3)锁消除
- (4)非公平锁
- (5)互斥锁
总结synchronized
初识synchronized
通过一个线程不安全的例子引入的synchronized
而出现该问题的原因就是此时的count++操作不是原子的
而我们通过引入synchronized就是将 count++ 这个操作打包成一个原子的操作
使用synchronized
使用synchronized需要针对一个"锁对象",在java里面,任何一个对象都可以作为锁对象
因此针对上述的代码,我们有几种加锁的方式
(1)直接对 count ++ 操作进行加锁
(2)在类里面的方法里面进行加锁
(3)直接对方法进行加锁
(4)针对类对象进行加锁(static方法)
对于这种,一旦有多个线程调用func,则这些线都会触发锁竞争,尽管不是针对同一个对象
synchronized的特性
(1)可重入性
我们在谈到死锁的时候,会出现的是下面这种情况:
按照正常逻辑,当我们针对两个相同的锁对象进行加锁操作的时候,如果先获取锁的一方没有释放锁,那么其他地方时不能获取到锁的
那么按照上面的代码,外层先拿到锁,内层的要获取到锁,就要等外层的释放锁,但是外层的释放锁,又要等内层的获取锁,就会造成死锁
而我们运行程序后发现:
程序是可以正常结束的
这就是synchronized的"可重入性"
实际上上述过程,对于synchronized是不适用的,但是在C++/Python就会出现死锁
是因为在synchronized里面内部自己做了特殊处理,在每一个锁对象里,.都会记录了是当前哪个线程持有了这个锁,当当前针对这个对象加锁操作时,就会先判定一下,当前尝试加锁的线程是否是持有当前锁的线程
如果不是,就阻塞,如果是,就放行
(2)自适应过程
synchronized的自适应过程如下
未加锁 ----(使用synchronized) -> 偏向锁 ----(产生锁冲突) --> 轻量级锁 —(锁冲突加强) --> 重量级锁
这里最主要的就是理解"偏向锁"
事实上,我们在使用synchronized的时候,一开始只是做了个"记号",并不是真正的加锁,这个记号非常轻量,几乎没有开销
此时如果后续不会产生锁冲突,那么就一直保持着偏向锁的状态
而一旦有别的线程想要获取到这把锁,那么synchronized就会立即转化为轻量级锁,此时就真正加锁了,就会产生锁冲突
(3)锁消除
如果你的代码里面加了锁,编译器就会自己帮你判断,这个地方是不是真的要加锁,如果不是,就会自动帮你把锁给优化掉
最典型的就是在单线程里面使用了synchronized
(4)非公平锁
synchronized是一个非公平锁
指的是,当多个线程都在阻塞等待,尝试获取同一把锁的时候,此时一旦锁释放,那么多个线程之间获取到锁的概率是等价的,就各凭本事了
而对公平锁,就是按照"先来后到"的顺序去获取锁,谁等待时间长了,谁就先拿到锁
(5)互斥锁
synchronized本身就是个互斥锁,读写锁则是更加特殊的一种锁
synchronize实际上就是两步,加锁和解锁,而读写锁要进行加读锁和加写锁
要实现,读与读之间不会产生互斥
但是写与写之间 , 读与锁之间就会产生互斥
注意;这里的加读锁和加写锁实际上和我们之前在mysql谈到的事务(读的时候不能写,写的时候不能读)不是一回事,事务本质上是在降低并发能力
而我们这里的读写锁是在提升并发能力,即"读操作和读操作"之间是共享的,不会引发互斥.有利于降低锁冲突的概率
在日常开发中,有很多场景都是属于"读多 写少",如果使用普通的互斥锁,此时,每次读操作之间,即使不会产生线程安全问题,也会互斥,此时就会比较影响效率