参考
https://blog.csdn.net/zqz_zqz/article/details/70233767
https://blog.csdn.net/noble510520/article/details/78834224
Java的锁是多线程技术中为了保证在多个线程同时工作下,变量一致性所引入的一种机制,在宏观上分为乐观锁和悲观锁两种。
-
乐观锁
乐观锁是体现了一种乐观的思想,即默认读多写少,遇到并发的可能性比较低。所以在每次读数据的时候都不会加锁。但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。 -
悲观锁
悲观锁默认是每一次都进行写入操作的,遇到并发的可能性更高,所以在每一次读写的时候都会进行加锁。
Java的悲观锁是synchronized。synchronized会使得没有争取到锁的线程进入阻塞状态,所以说它是一个重量级锁,为了缓解性能问题在JVM1.5以后开始,引入了轻量锁和偏向锁,并且默认启用了自旋锁,他们都是一种乐观锁
Java中每个对象都可以加锁,锁有四种级别,按照量级从轻到重分为:无锁、偏向锁、轻量级锁、重量级锁。每个对象一开始都是无锁的,随着线程间争夺锁,越激烈,锁的级别越高,并且锁只能升级不能降级。
偏向锁
偏向锁是Java6中引入的一项多线程的优化,它在编写的时候注明此处代码块需要进行加锁,那么就会首先进入偏向锁状态,当锁对象第一次被某个线程访问时,它会在其对象头的markOop中记录该线程ID,那么下次该线程再次访问它时,就不需要进行加锁了。但是这中间只要发生了其他线程访问该锁对象的情况,证明这个对象会发生并发,就不能对这个对象再使用偏向锁了,会将偏向锁升级为轻量级锁。
轻量级锁
轻量级锁是由偏向锁升级而来的,当第二个线程进入加锁的竞争代码块的时候,原来的偏向锁就会升级成为轻量级锁
与偏向锁的不同
- 轻量级锁每一次退出都需要释放锁,而偏向锁(由于是单一线程的加锁状态)所以只会在发生第二个线程竞争的时候才会释放锁
- 轻量级锁每次进入退出都需要同步CAS更新对象头
- 争夺轻量级锁失败时,自旋尝试抢占锁
重量级锁Synchronized
他可以把任何一个非null对象当做锁。synchronized是非公平锁
自旋锁
原理
如果持有锁的线程能够在很短的时间内释放锁,那么那些等待竞争锁的线程就不需要进行挂起操作,只需要等一等(自旋)就好,等待持有锁的线程释放锁后,就可以立即获得锁。这样可以避免用户线程和内核的切换的消耗
说白了就是这个等待线程让CPU在当前轮转时间片中做无用功,这样子会浪费一定的CPU性能。如果一直获取不到锁,那么当前线程就会一直跑下去,所以我们需要设置一个自旋等待时间,使得当等待时间过后,如果持有锁的线程扔没有释放锁,这时争用线程会停止自旋进入阻塞状态。
优点
自旋锁尽可能减少对线程的阻塞。这对锁的竞争并不激烈,且锁占用时间非常短的代码块的性能有着非常大的提升——因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗。
缺点
如果说锁的竞争激烈,或者所占用的时间比较长,那么就不适合使用自旋锁。因为自旋锁是在当前线程用死循环不停的去跑,所以就会占用过多的CPU资源。这时候我们就需要将自旋关闭,线程阻塞。
时间阈值
JVM在对自旋阈值的选择,在jdk1.5的时候是写死的,在jdk1.6的时候引入了适应性自旋锁,这就意味着自旋时间是不固定的了,而是由前一次在同一个锁上自旋的时间以及锁的拥有者的状态来决定,可以认为说一个线程上下文切换的时间是一个最佳的时间,同时JVM还针对当前CPU的负荷情况做了比较多的优化。
- 如果平均负载小于CPU时间片轮转则一直自旋
- 如果有超过两个CPU时间片/2个线程进行自旋,则后来的线程直接阻塞
- 如果正在自旋的线程发现Owner发生了变化则延迟自旋时间,或者进入阻塞。
- 自旋的时候会适当放弃线程优先级之间的差异。
重入锁
重入锁是指任意线程获取到锁后,再次获取该锁不会被该锁
网友评论