Java锁
不可不说的Java“锁”事 - 美团技术团队 (meituan.com)
乐观锁
对同一数据进行并发操作,乐观锁在进行操作数据时总是乐观认为不会有别的线程修改数据,因此它不会对资源加锁。当自己在进行数据修改之前它会先进行判断有没有其他线程修改了数据,如果数据没有修改,就写入数据。如果数据被修改了,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。
java中乐观锁基本上通过**CAS(Compare And Swap(比较与交换),是一种无锁算法)**操作实现的,,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
应用场景:适合读操作多的场景,不加锁的特性让读操作的性能得到很大的提升
悲观锁
对同一数据进行并发操作,悲观锁总认为别的线程会改写数据,因此在获取资源前它会对资源加锁,确保数据不会被更改。
Java中,synchronized关键字和Lock的实现类都是悲观锁。
应用场景:适合写操作多的场景,加锁的特性确保数据的准确性
自旋锁
多个线程并发操作同一个资源,发现该资源已经被占用,一般的情况是需要等待该资源的锁被释放之后,其他线程去争夺锁,获得锁的线程执行,其他线程等待(上下文的切换),从运行态转变为阻塞态,这个状态转换的过程需要耗费时间,如果说当前占用资源的线程运行的时长比其他线程状态转换时长还要短,那么可以考虑不需要进入阻塞状态,而是让线程等待一会(自旋一会),如果自旋之后资源已经被释放,那么就不必阻塞直接获取同步资源即可
![img](https://i-blog.csdnimg.cn/blog_migrate/6348ee6b1f70351dd5a5001e8e09815c.png)
注意:线程自旋的过程也是要消耗cpu的,因此需要设定一个自旋的时间(默认是10次,可以使用-XX:PreBlockSpin来更改),来确保资源能否在规定时间内获取到,如果获取不到,那么线程就从自旋转为阻塞。
优缺点:
- 自旋锁适合在对于资源竞争不激烈的情况下,尽量减少线程阻塞再到唤醒的过程,线程自旋的时间小于线程状态切换的时间,能够大幅度提升性能。
- 在资源竞争激烈或者持有同步锁的线程执行耗时很长,这个时候自旋锁就不适用。因为让线程一直占着cpu,但是线程又没有做事,纯属浪费资源和时间,并且此时其他需要cpu的线程无法得到,导致性能大大下降。
自旋锁开启
JDK1.4.2中引入,使用-XX:+UseSpinning来开启,-XX:PreBlockSpin 为自旋次数。
JDK1. 6中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。
适应性自旋锁
1.6之前的自旋锁的次数一定程度是写死的,之后的自适应锁次数不在固定,是由上一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定的。
如果对于同一个锁对象,自旋等待并且成功在自选周期内获得了锁资源,线程成功执行,那么JVM认为下一次这个自旋的过程很可能成功,因此会把自旋的周期持续相对更长的时间。反之,自旋过程很少获取锁资源,JVM认为这个自旋执行效率可能不高,可能考虑取消自旋进入阻塞状态,避免资源的浪费。
公平锁
多个线程按照顺序获取锁,需要获取锁的线程进入队列中等待,队列中最先发起请求锁的线程,也就是队列中的第一个线程能够最先获得锁,这就是公平锁。
好处:队列中等待锁资源的线程都能够获得锁,不会出现有些线程一直得不到锁的情况
缺点:整体吞吐量降低,因为每个没有获得锁的线程都要去队列中排队等待,cpu每次都要进行线程状态的转换,增加了开销
排队按顺序打水
![image-20210725104704513](https://i-blog.csdnimg.cn/blog_migrate/f43226ad569355fb5d110a0fa2be0cfe.png)
非公平锁
如果当前资源有其他线程正在时候,这个时候又来其他线程也需要这个资源,它并不会马上进入队列中等待,其他线程会尝试去等待锁资源的释放,如果获得了锁直接使用,不会去队列中等待(插队),如果没有获取到锁,它才会去队列中等待。
好处:减少了线程阻塞到唤醒的过程中的开销,整体吞吐效率提升
缺点:位于等待队列中的线程可能要等待很长时间才能获取到锁,甚至可能永远都获取不到
插队打水
![img](https://i-blog.csdnimg.cn/blog_migrate/84d19982aadd6cf81a3201969362ce74.png)
非公平锁实际执行的效率要远远超出公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。