java 锁的实现原理
java里面的锁,是由三个要素组成,分别是: 资源,线程,容器,整个的实现过程,概括起来很简单,过程如下:
线程开始尝试获取资源,这里的资源 一般指java类里面的成员变量,一般称其为许可证,这里的获取资源的过程,是所有的线程,并发获取,通过cas的方式实现最终只有一个线程获取资源成功.获取资源成功线程,记录当前拥有该资源的线程(基于锁的可重入性,进行设计),没有获取成功的线程加入等待容器(一般特指数组,链表,队列等存放资源的容器),并阻塞等待.成功获取资源的线程,执行完业务逻辑,开始释放资源(避免死锁,一般为了避免此类问题的出现,会设置锁住资源的时间),紧接着唤醒等待线程(这里可以单个唤醒,也可以整个唤醒,取决于等待容器的类型,已经唤醒的逻辑,一般如果,唤醒所有的线程,会出现"惊群效应")重新开始争抢获取资源.
总结来说,锁的实现,分为两个步骤:
一. 资源的获取与释放
二: 线程的阻塞与唤醒
首先,基于第一步,实现的方式,多种多样, 资源的获取与释放,按照数量分类:可以分为一次性单个获取和多个获取,因此可以分为独占锁和共享锁,按照资源获取顺序来分,可以按照先入队的先获取资源,后入队的后获取资源,因此 便有了公平锁和非公平锁.第二步: 线程的阻塞与唤醒,这一部分没有多样化的实现空间.但是在实现的细节上面还是有些些许不同:
阻塞与唤醒有三种不同的实现方式,分别是: suspend和resume,wait和notify/notifyAll,park和unpark,三种方式的侧重点都不同,一一介绍如下:
suspend,resume : 1. 在同步代码块中使用时,执行完之后 不释放锁,容易造成死锁 2.对两者的执行顺序要求比较严格,如果resume执行在了suspend前面也会造成死锁,极易形成死锁,已被弃用.
wait,notify/notifyAll: 1.必须在同一对象锁的同步代码块中调用.执行完之后,会释放锁,基于此,不会造成死锁,2. 但是对执行顺序有要求,wait必须执行在notify/notifyAll的前面,否则会形成死锁
locksuport.park/unpark: 1.内部是基于许可证的模式实现的,因此没有执行顺序上的要求,2.但是在同步代码块中执行,不会释放锁,因此也会有死锁的问题存在