作者
ERP组 陈 靖
推荐理由
该篇文章初步简要的写出了加锁和解锁的流程及原理,感兴趣的后来者可以通过该篇文章快速的了解加锁和解锁简要流程及原理。
1 锁的各种类型
1.1公平锁/非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。
多个非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序。非公平锁的优点在于吞吐量比公平锁大。
我们常用的synchronized就是一种非公平锁,而ReentrantLock则可以通过构造方法来指定是否公平锁。
1.2可重入锁
可重入锁是指,外层方法获取锁后,进入内层方法也会自动获取到锁。重入锁是为了一定程度上避免死锁,因为如果内层方法无法获取锁的话,那么外层方法将无法释放锁。
Reentrant就是可重入的意思。synchronized也是一种可重入锁。
1.3独享锁/共享锁(互斥锁/读写锁)(乐观锁/悲观锁)
这三种概念是类似的,独享锁是指每个线程独享的,当其中一个线程获取到锁时,另外的线程申请锁时将进入阻塞状态;共享锁是指可以由多个线程共享;
互斥锁对应独享锁,ReentrantLock就是一种独享锁;而读写锁对应共享锁,因为读操作是可以共享的,ReadWriteLock是Java读写锁的一种实现方式;
悲观锁是指在开发过程中同一个数据发生并发操作时一定会发生修改的,所以锁应该独享;乐观锁是指发生并发时数据不会发生修改,所以是可以共享的;
1.4分段锁
分段锁是指一种锁的设计,目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
1.5偏向锁/轻量级锁/重量级锁
这三种锁是指锁的状态,并且是针对synchronized。在Java 5通过引入锁升级的机制来实现高效synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
1.6自旋锁
在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
2 ReentrantLock构造方法
2.1构造方法主体,从命名规则上可以看出,默认的构造方法是非公平锁,第二种构造方法则可以通过入参指定公平锁还是非公平锁。
2.2 lock与unlock,可以看到内部调用的主要是sync的方法,所以我们主要就是要研究sync内部是如何实现的。
2.3通过类的继承关系图,可以看到两种实现方式都是继承于AbstractQueuedSynchronizer(下面简称AQS),通过命名可以看出这是一种基于队列的同步机制。
通过查阅资料,我了解到AQS为构建不同的同步组件(重入锁,读写锁,CountDownLatch等)提供了可扩展的基础框架。AQS以模板方法模式在内部定义了获取和释放锁的模板方法,由子类决定在获取和释放锁时的细节,从而实现满足自身功能特性的需求。除此之外,AQS通过内部的同步队列管理获取同步状态失败的线程,向实现者屏蔽了线程阻塞和唤醒的细节。
3 非公平锁的实现
3.1 NonfairSync中的lock的实现
可以看到lock 方法中先进入了一个compareAndSetState方法,尝试快速获取锁,以cas(比较和交换Compare And Swap是用于实现多线程同步的原子指令)的方式将state的值更新为1,只有当state的原值为0时更新才能成功。
更新成功就表示获取锁成功,更新失败则进入acquire方法进一步获取锁
acquire的实现
可以看出会先执行tryAcquire 方法,而NonfairSync的tryAcquire方法调用的是Sync中的nonfairTryAcquire方法,可以看出该方法先判断state的值是否为0,为0则可以加锁成功;不为0的话则再判断是否在同一个线程中,在同一个线程中返回的也是true,并且将state加一;从这里可以看出state表示加锁次数(重入次数);
加锁失败,将执行addWaiter方法,为当前线程创建节点并添加到队列中。
然后执行acquireQueued方法,从这里可以看出,这里有一个无限的for循环,只有获取到锁后才return出去。
第一个if分支,如果前置节点是头结点,则再次去获取锁,获取成功才return
第二个if分支从方法的命名上可以看出是阻塞线程,从方法内部可以看出,未获取到锁 时线程不会马上阻塞,会有第二次循环的机会。
3.2 NonfairSync中的unlock的实现
唤醒线程
-
公平锁的实现方式
4.1 对比非公平锁的方法,发现当state等于0时,公平锁会多一个hasQueuedPredecessors的判断,从名字上我们可以看出是否有前置的节点;进入方法,我们看到,他判断了头不等于尾,并且头节点的next指向的节点的线程不是本节点。这种情况下将不去获取锁。
总结
•1.熟悉了ReentrantLock的内部构造以及加锁和解锁的流程,理解了非公平锁和公平锁实现的本质区别以及为何前者相比后者有更好的性能。以此为基础,我们可以更好的使用ReentrantLock。
•2.通过对部分实现细节的学习,了解了如何以CAS算法构建无锁的同步队列,我们可以借鉴并以此来构建自己的无锁的并发容器。