Java锁机制梳理与详细介绍

Posted 小韵豆子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java锁机制梳理与详细介绍相关的知识,希望对你有一定的参考价值。

新年第一天上班,除了划水也只能是划水。之前对Java各种锁理解的比较片面,也没有将他们归类好,借此良机好好回顾总结~

Java锁机制梳理

Java中的锁非常丰富,可以通过不同的特性选择使用对应的锁。当然,适当的业务场景下用对锁也是性能高低的关键。

先来个全貌图(Java中的各种锁),有没有一图搞懂Java锁的意思。如图,是按照Java锁的特性来归类的:

 

Java锁机制详细介绍

乐观锁/悲观锁

乐观锁:线程去更新某一共享数据时,认为其它线程不会更新,所以不会针对此共享数据上锁,但在更新此共享数据时会判断是否有被其它线程更新过。

              可以使用CAS算法实现,java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

              乐观锁适用于多读的业务操作,这样可以提高吞吐量。

悲观锁:

                     线程去更新某一共享数据时,认为其它线程也会更新,所以更新此共享数据是会上锁,导致其它线程来更新共享数据获取锁时被阻塞,只能等到锁被释放。

             Java中synchronizedReentrantLock等独占锁就是悲观锁的实现。

             悲观锁适用于多写的业务操作,这样可以保证数据的正确性。

 

自旋锁/适应性自旋锁

自旋锁:

    在某些业财场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。

    如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃 CPU 的执行时间,看看持有锁的线程是否很快就会释放锁。

    而为了让当前线程“再次获取”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销,这就是自旋锁。

    自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。

    所以,自旋等待的 时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。

 

适用性自旋锁:

                  适用性自旋锁就是解决自旋锁的缺点的,JDK1.6自旋锁默认开启,而且添加了适用性自旋锁。

                  有了适用性自旋锁就意味着自旋锁的自旋次数或者时间不是固定不变的,而是根据同一个锁获取锁的时间和状态来确定的。

 

无锁/偏向锁/轻量级锁/重量级锁

                无锁/偏向锁/轻量级锁/重量级锁是Synchronized是四种状态。

                这四种状态是在jdk1.6之后引入的,分别为:(无锁->偏向锁->轻量级锁->重量级锁 ) 这几个状态会随着竞争情况逐渐升级,锁状态只能升不能降级。

 

 

无锁:

            无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

            无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。也就是CAS(CAS是基于无锁机制实现的)。

偏向锁:

                       偏向锁是指某线程执行同步代码或同步方法时,被确定为偏向锁,当该线程再一次执行此同步代码或同步方法时该线程会自动获取锁,降低获取锁的代价。

                偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。

                       在 JDK 1.6 中引入,默认启用,且会延迟启动,关闭延迟:-XX:BiasedLockingStartupDelay=0,关闭偏向锁:-XX:-UseBiasedLocking=false,默认会进入轻量级锁。

轻量级锁:

                轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋获取锁,线程不会阻塞,从而提高性能。

                 轻量级锁的获取主要由两种情况:① 当关闭偏向锁功能时;② 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁。

                 轻量级锁自旋超过一定的次数时,轻量级锁便会升级为重量级锁(锁膨胀)。

                当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来获取锁,轻量级锁也会升级为重量级锁(锁膨胀)。

重量级锁:

              重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。

              重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

 

 

公平锁/非公平锁

公平锁:

            公平和非公平锁的队列都基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。公平锁则在于每次都是依次从队首取值。

非公平锁:

                非公平锁获取锁的机制是随机的,也有可能一直获取不到锁。

               非公平锁性能高于公平锁性能。首先,在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。而且,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。

 

 

可重入锁锁/非可重入锁

可重入锁:

               可重入锁同一个线程可以再次获取方法的同一个锁(必须是同一个对象的锁),再次获取锁时不会因为之前这个线程已经获取过锁了而需要阻塞等待。

              Java中ReentrantLock与synchronized都是可重入锁,可重入锁的优点是可以避免死锁。

非可重入锁:

                    非可重入锁同一个线程如果已经获取到锁后再次去获取锁会获取失败,线程会阻塞等待。

 

 

共享锁/独占锁

 

        共享锁:

                     共享锁是指线程A对数据A加了共享锁,线程A可以读针对数据A读写操作,而其它线程只能获取数据A的共享锁,只能对数据读,不能写。

                  

         独占锁:

                    独占锁也叫排它锁,线程A个数据A加了锁之后其它锁不可以再对数据A加任何锁。

 

 

 

 

 

 

 

以上是关于Java锁机制梳理与详细介绍的主要内容,如果未能解决你的问题,请参考以下文章

java中的各种锁详细介绍

java中的各种锁详细介绍

乐观锁与悲观锁

架构之路—java开发必学知识点详细梳理

Java并发编程:并发编程基础各种锁详细介绍

收藏夹吃灰系列:一篇文教你如何快速实现乐观锁机制及适用场景 | 超级详细,建议收藏!