1 Atomic系列优化加锁并发性能
1.0 i++和ActomicInteger之间的差别分析
static Integer i = 0 ;
static AtomicInteger j = new AtomicInteger ( 0 ) ;
private static void synchronizedAdd ( ) {
for ( int i = 0 ; i < 10 ; i++ ) {
new Thread ( ) {
@Override
public void run ( ) {
synchronized ( AtomicIntegerDemo . class ) {
System . out. println ( AtomicIntegerDemo . i++ ) ;
}
}
} . start ( ) ;
}
}
private static void atomicAdd ( ) {
for ( int i = 0 ; i < 10 ; i++ ) {
new Thread ( ) {
@Override
public void run ( ) {
}
} . start ( ) ;
}
}
这俩个方法实现同样的一个功能,但是,第一种进行加锁的方式,导致线程串行化的一种操作,同一时间只能有一个线程获取到锁,对值进行操作,
第二种方式就不是加锁的效果了,它是用的原子类,他是多个线程并发来运行,可以保证原子性
1.1 AtomicInteger中的CAS无锁化原理
底层是一个无锁化的思想,也是一个乐观锁的思想
判断此时此刻是否是某个值,如果是则修改,如果不是则重新查询一个最新的值,再次执行判断,这个操作叫做CAS ( compare and swap)
Atomic 的层原理就是CAS 可以叫做是无锁的,也可以是乐观锁的思想,每次尝试修改的时候,就对比一下有没有人修改过这个值,没有人修改,那么就自己修改,如果有人修改,那么就重新查出最新的值,进行修改,再次重复那个过程
1.2 AtomicInteger源码剖析:仅限JDK内部使用的Unsafe类
private volatile int value;
public AtomicInteger ( int initialValue) {
value = initialValue;
}
这里保证了一个可见性,保证一个线程将值修改后,其他值可以快速的看见它,
atomicInteger. incrementAndGet ( ) ;
public final int incrementAndGet ( ) {
return unsafe. getAndAddInt ( this , valueOffset, 1 ) + 1 ;
}
Unsafe 类是在JDK 底层的一个类,而且话人家限制好了,不允许你实例化它以及使用它里面的方法的,首先人家的构造函数是私有化的,不能自己手动去实例化它,其次,如果用Unsafe . getUnsafe ( ) 方法来获取一个实例也是不行的,再那个源码里,他会判断一下,如果当前是属于我们的用户的应用系统,识别到由我们的类加载器以后,就会报错,不让我们来获取实例
只能是JDK 源码里面,JDK 自己内部来使用,不是对外的
Unsafe ,封装了一些不安全的操作,指针相关的一些操作,就是比较底层了,主要是Atomic 原子类底层用到了大量的unsafe
1.3 AtomicInteger源码剖析:无线重复循环以及CAS操作
private static final long valueOffset;
static {
try {
valueOffset = unsafe. objectFieldOffset
( AtomicInteger . class . getDeclaredField ( "value" ) ) ;
} catch ( Exception ex) { throw new Error ( ex) ; }
}
private volatile int value;
类初始化的时候,来进行执行的,valueOffset,value这个字段在AtomicInteger 这个中的偏移量,在底层类是由自己对应的结构的,无论是在磁盘的. class 文件里的,还是在jvm内存中
大概可以理解为:value这个字段具体是在AtomicInteger 这个类的哪个位置,offset,偏移量,这个是很底层的操作,是通过unsafe来实现的,刚刚在类初始化的时候就会完成这个操作,一旦初始化完 就不会变更了
public final int getAndAddInt ( Object var1, long var2, int var4) {
int var5;
do {
var5 = this . getIntVolatile ( var1, var2) ;
} while ( ! this . compareAndSwapInt ( var1, var2, var5, var5 + var4) ) ;
return var5;
}
compareAndSwapInt ( ) , CAS 的方法
它会拿你刚刚这个获取到的var5的值,去跟底层目前这个AtomicInteger 的对象实例中的value的值进行比较,如果是一样的话,就会执行swap的这个过程,var5+ var4 ( 1 ) , 如果获取到的值,和AtomicInteger + valueOffset获取到的当前的值不一样的话,此时compareAndSwapInt方法就会返回false , 如果返回false 的话,那么就会进行下一轮循环,找到当前value的值,然后进行比对,如果返回var5的值,如果不成功,那么就进入下一轮循环,在外层方法中+ 1 返回
1.4 AtomicInteger源码剖析:底层cpu指令是如何实现CAS语义的
最最底层是用了一个native 的代码,不是用java写的,用C 写的代码,可以通过发送一些cpu的指令,来确保说CAS 的那个过程,绝对是原子的,具体是怎么来实现的?以前的cpu会通过一些指令来锁掉某一小块的内存,后面会做一些优化,它可以保证仅仅有一个线程campare-> set,这是一系列的步骤,在执行这个步骤的话,是每个线程是原子的,有一个线程在执行CAS 一系列的比较和设置的过程中,其他的线程是不能来执行的,
Cpu 指令来实现
Cpu 会通过一些轻量级的锁小块内存的机制来实现,保证整个并发的性能要好的多
1.5 Atomic原子类体系的CAS语义存在的三大缺点分析
1 、ABA 问题:如果某个值一开始是A ,后来变成了B ,然后又变成了A ,你本来期望的是值如果是第一个A 才会设置新值,结果第二个A 一比较也ok,也设置了新值,跟期望是不符合的。所以atomic包里有AtomicStampedReference 类,就是会比较两个值的引用是否一致,如果一致,才会设置新值
假设一开始变量i = 1 ,你先获取这个i的值是1 ,然后累加了1 ,变成了2
但是在此期间,别的线程将i -> 1 -> 2 -> 3 -> 1
这个期间,这个值是被人改过的,只不过最后将这个值改成了跟你最早看到的值一样的值
结果你后来去compareAndSet的时候,会发现这个i还是1 ,就将它设置成了2 ,就设置成功了
说实话,用AtomicInteger ,常见的是计数,所以说一般是不断累加的,所以ABA 问题比较少见
2 、无限循环问题:大家看源码就知道Atomic 类设置值的时候会进入一个无限循环,只要不成功,就不停循环再次尝试,这个在高并发修改一个值的时候其实挺常见的,比如你用AtomicInteger 在内存里搞一个原子变量,然后高并发下,多线程频繁修改,其实可能会导致这个compareAndSet ( ) 里要循环N 次才设置成功,所以还是要考虑到的。导致cpu的负载比较高
JDK 1.8 引入的LongAdder 来解决,是一个重点,分段CAS 思路
3 、多变量原子问题:一般的AtomicInteger ,只能保证一个变量的原子性,但是如果多个变量呢?你可以用AtomicReference ,这个是封装自定义对象的,多个变量可以放一个自定义对象里,然后他会检查这个对象的引用是不是一个。
1.6 LongAdder是如何通过分段CAS机制优化多线程自旋问题
Java 8 提供的一个对AtomicLong 改进后的一个类,LongAdder
大量线程并发更新一个原子类的时候,天然的一个问题就是自旋,会导致并发性能还是有待提升,比synchronized 当然好很多了
分段迁移,某一个线程如果对一个Cell 更新的时候,发现说出现了很难更新他的值,出现了多次自旋的一个问题,如果他CAS 失败了,自动迁移段,他会去尝试更新别的Cell 的值,这样的话就可以让一个线程不会盲目的等待一个cell的值
2 AQS
2.0 尝试一下用另一种锁ReentractLock
可重入锁
我们多线程需要加锁,除了用synchronized 关键字,然后加wait+ notify的方式,其实 Lock 锁Api ,来加锁和释放锁
public class ReentractLockDemo {
static volatile int i = 0 ;
static ReentrantLock lock = new ReentrantLock ( ) ;
public static void main ( String args[ ] ) {
new Thread ( ) {
public void run ( ) {
for ( int i = 0 ; i < 10 ; i++ ) {
lock. lock ( ) ;
ReentractLockDemo . i++ ;
System . out. println ( ReentractLockDemo . i) ;
lock. unlock ( ) ;
}
}
} . start ( ) ;
new Thread ( ) {
public void run ( ) {
for ( int i = 0 ; i < 10 ; i++ ) {
lock. lock ( ) ;
ReentractLockDemo . i++ ;
System . out. println ( ReentractLockDemo . i) ;
lock. unlock ( ) ;
}
}
} . start ( ) ;
lock. lock ( ) ;
lock. unlock ( ) ;
}
}
上边的这种写法相当于是我们加synchronized 的这种方式,那么有synchronized 为什么还要用lock的这种锁呢?
因为,lock锁API ,来加锁和释放锁,可以加读写锁,非常常用,也很有用
2.1 谈谈你对AQS的理解
AQS ( AbstractQueuesSynchronizer , 抽象队列同步器)
ReentractLock / ReadWriteReentractLock , 锁API 底层都是基于AQS 来实现的,一般我们自己不直接使用,但是是属于java并发包里的底层的API ,专门支撑各种java并发类的底层的逻辑实现
2.2 ReentractLock底层原来是基于AQS实现锁的
public ReentrantLock ( ) {
sync = new NonfairSync ( ) ;
}
默认的构造函数这里,创建了一个sync,Nonfairsync , 看起来是一个非常关键的一个组件,很可能是底层专门用来底层的加锁和释放锁
public void lock ( ) {
sync. lock ( ) ;
}
ReenactLock 在进行加锁的时候,他其实是直接基于底层的Sync 来实现的lock操作,但是如果是这个样子的话,ReentractLock 这个类其实就是比较外层的一个薄薄的封装的一个类,Sync 就是ReenactLock 底层的核心组件
Sync :关键组件
abstract static class Sync extends AbstractQueuedSynchronizer
Sycn 是抽象的静态内部类,是AbstractQueuedSynchronizer 的子类,这个东西抽象队列同步器,是java并发包各种并发工具(锁/ 同步器)的底层的基础性的组件,主要是以来它来实现各种锁
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java. io. Serializable {
}
这个AQS 类里面都有些什么呢?
static final class Node {
}
这个node是自定义数据结构,可以组成一个双向链表,也就是所谓的队列
private volatile int state;
核心变量,加锁/ 释放锁都是基于state来完成的
Sync 就是AQS (子类的实现,多线程同步组件的一个意思)
NonfairSync ( ) 非公平的同步实现
它是sync的一个子类,覆盖重写的几个方法
所以我们也可以认为ReentractLock -- > Synchronized 功能是一样的(可重入加锁)
2.3 AQS如何基于无锁化的CAS机制实现高性能加锁
final void lock ( ) {
if ( compareAndSetState ( 0 , 1 ) )
setExclusiveOwnerThread ( Thread . currentThread ( ) ) ;
else
acquire ( 1 ) ;
}
AQS 底层加锁,释放锁,都是大量的基于CAS 的操作来实现的,底层是基于NonfairSync 的lock操作来实现加锁的
if ( compareAndSetState ( 0 , 1 ) )
AQS 中有一个核心的变量,state, 代表了锁的状态,看一下state是否是0 ,如果是0 ,代表没人加锁,那么我就会加锁,那么我就把state改成1 ,
CAS 可以无锁化的保证一个数值的修改
compareAndSetState ( 0 , 1 )
这个相当于是尝试在加锁,底层原来是基于unsafe来实现的,JDK 内部使用的API ,指针操作,基于CPU 指令是实现原子性的CAS ,Atomic 元子类系列,也是基于Unsafe 来实现CAS 操作
return unsafe. compareAndSwapInt ( this , stateOffset, expect, update) ;
这行代码可以保证说,在一个原子操作中,如果发现值是我们期望的expect值,说明符合要求,没人修改过,此时可以将这个值设置为update,state如果是0 的话,就修改为1 ,代表加锁成功了
如果加锁成功了,那么就会执行到setExclusiveOwnerThread ( Thread . currentThread ( ) ) ; 设置当前线程自己是加了一个独占锁的线程,标识出来,自己是加锁的线程
2.4 如何巧妙的借助AQS中的state变量实现可重入式加锁
假如说此时,线程1 再次进入,可重入加锁
如果是一个线程可重入的加锁,会是什么样子的?如何实现的?
可重入加锁那么if ( compareAndSetState ( 0 , 1 ) ) 就会返回一个false ,然后执行到以下代码
acquire ( 1 ) ;
public final void acquire ( int arg) {
if ( ! tryAcquire ( arg) &&
acquireQueued ( addWaiter ( Node . EXCLUSIVE ) , arg) )
selfInterrupt ( ) ;
}
这个方法会走到AQS 的方法中去,
protected final boolean tryAcquire ( int acquires) {
final Thread current = Thread . currentThread ( ) ;
int c = getState ( ) ;
if ( c == 0 ) {
if ( ! hasQueuedPredecessors ( ) &&
compareAndSetState ( 0 , acquires) ) {
setExclusiveOwnerThread ( current) ;
return true ;
}
}
else if ( current == getExclusiveOwnerThread ( ) ) {
int nextc = c + acquires;
if ( nextc < 0 )
throw new Error ( "Maximum lock count exceeded" ) ;
setState ( nextc) ;
return true ;
}
return false ;
}
首先先得到当前的一个线程,然后获取到state变量值的一个过程
进入到这个方法里面,那么代表之前state一定不是0 ,才会进入到这里,但是为什么又会有判断c == 0 这个分支,其实就是代码的一个健壮性,怕之前那个state不为0 ,所以加锁失败了,但是进入到这里,人家再次判断一下,如果state是0 , 那么再次加速哦,就怕中间有人释放掉锁,一般会走到else ,再次判断,如果执行方法的线程就等于加锁的线程,代表是一个线程在可重入的加锁,之前它已经加过锁,但是这里再次的加锁,此时就会将state设置为2
2.5 AQS的本质:为啥叫抽象队列同步器?
public final void acquire ( int arg) {
if ( ! tryAcquire ( arg) &&
acquireQueued ( addWaiter ( Node . EXCLUSIVE ) , arg) )
selfInterrupt ( ) ;
}
如果不是一个线程重复加锁的话,那么就会走到同步器这块
acquireQueued ( addWaiter ( Node . EXCLUSIVE ) , arg) 将当前线程入队阻塞等待
private Node addWaiter ( Node mode) {
Node node = new Node ( Thread . currentThread ( ) , mode) ;
Node pred = tail;
if ( pred != null ) {
node. prev = pred;
if ( compareAndSetTail ( pred, node) ) {
pred. next = node;
return node;
}
}
enq ( node) ;
return node;
}
EXCLUSIVE ,排他性,独占锁,同一时间只能有一个线程获取到锁,此时是排他锁,
首先将当前线程(线程二)封装成了一个Node ,mode = EXCLUSIVE , ( 排他锁)
Node 中包含了一些什么东西
volatile int waitStatus;
如果一个线程无法获取到锁的话,会进入到一个阻塞等待的状态,卡住不动,线程挂起,阻塞的状态又细分为很多种不同的状态;
volatile Node prev;
一个节点可以有上一个节点,prev指针,指向了Node 的上一个Node
volatile Node next;
一个节点还可以有下一个节点,next指针,指向了Node 的下一个Node
volatile Thread thread;
当前的node里面封装了一个线程
Node nextWaiter;
下一个等待线程
获取不到锁,处于等待状态的线程,会封装为一个Node ,而且有指针,最后多个处于阻塞等待状态的线程可以封装为node的这样一个双向链表。其实就是队列的一个实现
2.6 加锁失败的时候如何借助AQS异步入队阻塞等待?
如果加锁失败的话,它会把自己封装成一个Node , Node 原来是AQS 底层非常关键的一个数据结构,双向链表,估计就会将当前的线程封装为Node 入队,阻塞等待释放这个锁
private Node enq ( final Node node) {
for ( ; ; ) {
Node t = tail;
if ( t == null ) {
if ( compareAndSetHead ( new Node ( ) ) )
tail = head;
} else {
node. prev = t;
if ( compareAndSetTail ( t, node) ) {
t. next = node;
return t;
}
}
}
}
private final boolean compareAndSetHead ( Node update) {
return unsafe. compareAndSwapObject ( this , headOffset, null , update) ;
}
headOffset -> 在AQS 类里,head变量所在的位置,CAS 操作,判断一下,head变量是否为null , 如果是null 的话,就将head设置为空Node 节点
acquireQueued ( addWaiter ( Node . EXCLUSIVE ) , arg)
addWaiter其实就是将线程二加入到双向链表中。
final boolean acquireQueued ( final Node node, int arg) {
boolean failed = true ;
try {
boolean interrupted = false ;
for ( ; ; ) {
final Node p = node. predecessor ( ) ;
if ( p == head && tryAcquire ( arg) ) {
setHead ( node) ;
p. next = null ;
failed = false ;
return interrupted;
}
if ( shouldParkAfterFailedAcquire ( p, node) &&
parkAndCheckInterrupt ( ) )
interrupted = true ;
}
} finally {
if ( failed)
cancelAcquire ( node) ;
}
}
首先获取到node的上一个节点,其实是一个空的node
这个地方,其实会再次调用tryAcquire ( arg) 方法尝试加锁,如果加锁成功,其实会将线程2 对应的Node 从队列中移除
if ( shouldParkAfterFailedAcquire ( p, node) ,如果说再次尝试加锁失败了,那么此时会判断一下,是否需要将当前的线程挂起,阻塞等待,如果是需要的话,此时就会用park操作挂起线程
private static boolean shouldParkAfterFailedAcquire ( Node pred, Node node) {
int ws = pred. waitStatus;
if ( ws == Node . SIGNAL )
return true ;
if ( ws > 0 ) {
do {
node. prev = pred = pred. prev;
} while ( pred. waitStatus > 0 ) ;
pred. next = node;
} else {
compareAndSetWaitStatus ( pred, ws, Node . SIGNAL ) ;
}
return false ;
}
private final boolean parkAndCheckInterrupt ( ) {
LockSupport . park ( this ) ;
return Thread . interrupted ( ) ;
}
2.7 AQS默认的非公平加锁策略的运作原理
非公平锁和公平锁
分布式锁底层原理,公平必须严格排队,非公平就是随意抢占
非公平,哪怕是有很多的线程在队列中排队,但是当某个线程释放锁的一瞬间,很可能会有其他的晚到的线程突然争抢到锁,就导致先来的线程还在排队
2.8 ReentractLock如何设置公平锁策略以及原理
如果说,你希望每个人过来都要按照顺序排队来加锁,那就是公平锁了,每个人先来后到,先来的人先加锁,只要你在初始化的时候传一个参数( true )
ReentrantLock lock = new ReentrantLock ( true ) ;
public ReentrantLock ( boolean fair) {
sync = fair ? new FairSync ( ) : new NonfairSync ( ) ;
}
if ( c == 0 ) {
if ( ! hasQueuedPredecessors ( ) &&
compareAndSetState ( 0 , acquires) ) {
setExclusiveOwnerThread ( current) ;
return true ;
}
}
公平锁的核心就是那一行代码,如果前面没有排队等待的线程我就尝试加锁,如果有的话,我是不能加锁的
公平锁,任何一个线程过来,先判断一下,当前是否有人在排队,而且是不是自己在排队,如果不是的话,那么有别人在排队,那么自己不能加锁,直接入队等待
2.9 tryLock如何实现加锁等待一段时间后放弃
Boolean result = lock. tryLock ( 10 , TimeUnit . SECONDS ) ;
if ( result) {
} else {
}
等待10 s,如果超时那么就加锁失败,在实际开发的时候,我们可能会用到tryLock操作,有的时候不希望一直阻塞在那里尝试加锁
2.10 基于AQS实现可重入锁释放源码剖析
public final boolean release ( int arg) {
if ( tryRelease ( arg) ) {
Node h = head;
if ( h != null && h. waitStatus != 0 )
unparkSuccessor ( h) ;
return true ;
}
return false ;
}
释放锁,线程1 加了锁,总得释放
如何把锁给释放掉,另外一个是如果锁彻底释放了以后,如何让队列中的对头的那个线程来唤醒尝试获取锁
protected final boolean tryRelease ( int releases) {
int c = getState ( ) - releases;
if ( Thread . currentThread ( ) != getExclusiveOwnerThread ( ) )
throw new IllegalMonitorStateException ( ) ;
boolean free = false ;
if ( c == 0 ) {
free = true ;
setExclusiveOwnerThread ( null ) ;
}
setState ( c) ;
return free;
}
可重入加锁,加了2 次,释放锁1 次,再次释放锁
c = 1 - 1 = 0
2.11 锁释放后如何对AQS队列中唤醒阻塞线程尝试抢占锁
unparkSuccessor ( h) ;
private void unparkSuccessor ( Node node) {
int ws = node. waitStatus;
if ( ws < 0 )
compareAndSetWaitStatus ( node, ws, 0 ) ;
Node s = node. next;
if ( s == null || s. waitStatus > 0 ) {
s = null ;
for ( Node t = tail; t != null && t != node; t = t. prev)
if ( t. waitStatus <= 0 )
s = t;
}
if ( s != null )
LockSupport . unpark ( s. thread) ;
}
如果一个线程来释放锁的话,他除了更新state和锁占有线程以外,它其实就是用LockSupport 的unpark操作来唤醒了一个处于队头的线程
队头的线程此时被unpark唤醒后要干什么
private final boolean parkAndCheckInterrupt ( ) {
LockSupport . park ( this ) ;
return Thread . interrupted ( ) ;
}
线程是在上边的那个方法被挂起的,当它被唤醒就是返回了一个线程的interrupted
for ( ; ; ) {
final Node p = node. predecessor ( ) ;
if ( p == head && tryAcquire ( arg) ) {
setHead ( node) ;
p. next = null ;
failed = false ;
return interrupted;
}
if ( shouldParkAfterFailedAcquire ( p, node) &&
parkAndCheckInterrupt ( ) )
interrupted = true ;
}
如果一旦被unpark唤醒后,就会在这里苏醒过来,然后重新进入for 循环中再次执行
此时线程二会再次尝试获取锁,因为它是队头的线程,
2.12 读锁和写锁分开
synchronized 和ReentrantLock 是一样的,如果真正的开发推荐首先用synchronized ,
Lock API 的话,就是使用的ReentrantReadWriteLock 比较多,读写锁,可以加读锁,也可以加写锁,但是读锁和写锁是互斥的,也就是说,你加了写锁后就不能加写锁,如果加了写锁就不能加读锁
但是如果有人加了读锁之后,别人可以同时加读锁,
如果你有一份数据,有人读,有人写,如果你全部都是用synchronized 的话,会导致如果多个人读,也是要串行化的,一个接一个的读
我们希望的效果是,多个人都可以同时来读,如果使用读锁和写锁分开的方式,就可以让多个人来读数据,因为多个人可以同时来加读锁,如果有人在读数据,就不能有人写数据,读锁和写锁是互斥的,如果有人在写数据,别人不能写数据,写锁和写锁也是互斥的
ReentrantReadWriteLock lock = new ReentrantReadWriteLock ( ) ;
lock. readLock ( ) . lock ( ) ;
lock. readLock ( ) . unlock ( ) ;
lock. writeLock ( ) . lock ( ) ;
lock. writeLock ( ) . unlock ( ) ;
2.13 读写锁中的谢锁是如何基于AQS的state变量完成加锁的
protected final boolean tryAcquire ( int acquires) {
Thread current = Thread . currentThread ( ) ;
int c = getState ( ) ;
int w = exclusiveCount ( c) ;
if ( c != 0 ) {
if ( w == 0 || current != getExclusiveOwnerThread ( ) )
return false ;
if ( w + exclusiveCount ( acquires) > MAX_COUNT )
throw new Error ( "Maximum lock count exceeded" ) ;
setState ( c + acquires) ;
return true ;
}
if ( writerShouldBlock ( ) ||
! compareAndSetState ( c, c + acquires) )
return false ;
setExclusiveOwnerThread ( current) ;
return true ;
}
2.14 基于AQS的state二进制高低16位判断实现写锁的可重入加锁
写锁的可重入加锁,state的二进制的高低16 位的一个判断
c!= 0 , w== 0 c肯定不是0 ,但是低16 位是0 ,说明有人加了读锁,没有人加写锁,此时你要加写锁,你还不是加锁的线程,那么就直接返回,因为这里是互斥的
c!= 0 , w== !0 ,那么有人加过锁,之前加的是写锁,但是当前线程不是之前加锁的那个线程,此时也不让你加锁,同一时间,只能有一个线程加写锁,如果线程1 加了写锁,线程二也要加写锁是不行的
c!= 0 , w== !0 ,那么有人加过锁,之前加的是写锁,而且加写锁的是你自己如果加写锁的是你自己,那么你就是在可重入的加写锁setState ( c + acquires) ; 此时将state+= 1
if ( c != 0 ) {
if ( w == 0 || current != getExclusiveOwnerThread ( ) )
return false ;
if ( w + exclusiveCount ( acquires) > MAX_COUNT )
throw new Error ( "Maximum lock count exceeded" ) ;
setState ( c + acquires) ;
return true ;
}
2.15 写锁加锁失败时如何基于AQS队列完成入队阻塞等待?
线程1 加了俩次写锁,此时线程2 来加锁会出现什么情况?
public final void acquire ( int arg) {
if ( ! tryAcquire ( arg) &&
acquireQueued ( addWaiter ( Node . EXCLUSIVE ) , arg) )
selfInterrupt ( ) ;
}
线程2 如果来加写锁,那么会被互斥,会被卡住,阻塞,在队列中等待唤醒
2.16 读写锁互斥:基于AQS的state二进制高低16位完成互斥判断
读锁源码
public final void acquireShared ( int arg) {
if ( tryAcquireShared ( arg) < 0 )
doAcquireShared ( arg) ;
}
protected final int tryAcquireShared ( int unused) {
Thread current = Thread . currentThread ( ) ;
int c = getState ( ) ;
if ( exclusiveCount ( c) != 0 &&
getExclusiveOwnerThread ( ) != current)
return - 1 ;
int r = sharedCount ( c) ;
if ( ! readerShouldBlock ( ) &&
r < MAX_COUNT &&
compareAndSetState ( c, c + SHARED_UNIT ) ) {
if ( r == 0 ) {
firstReader = current;
firstReaderHoldCount = 1 ;
} else if ( firstReader == current) {
firstReaderHoldCount++ ;
} else {
HoldCounter rh = cachedHoldCounter;
if ( rh == null || rh. tid != getThreadId ( current) )
cachedHoldCounter = rh = readHolds. get ( ) ;
else if ( rh. count == 0 )
readHolds. set ( rh) ;
rh. count++ ;
}
return 1 ;
}
return fullTryAcquireShared ( current) ;
}
private void doAcquireShared ( int arg) {
final Node node = addWaiter ( Node . SHARED ) ;
boolean failed = true ;
try {
boolean interrupted = false ;
for ( ; ; ) {
final Node p = node. predecessor ( ) ;
if ( p == head) {
int r = tryAcquireShared ( arg) ;
if ( r >= 0 ) {
setHeadAndPropagate ( node, r) ;
p. next = null ;
if ( interrupted)
selfInterrupt ( ) ;
failed = false ;
return ;
}
}
if ( shouldParkAfterFailedAcquire ( p, node) &&
parkAndCheckInterrupt ( ) )
interrupted = true ;
}
} finally {
if ( failed)
cancelAcquire ( node) ;
}
}
2.17 释放写锁的源码剖析以及对AQS队列唤醒阻塞线程
public void unlock ( ) {
sync. release ( 1 ) ;
}
public final boolean release ( int arg) {
if ( tryRelease ( arg) ) {
Node h = head;
if ( h != null && h. waitStatus != 0 )
unparkSuccessor ( h) ;
return true ;
}
return false ;
}
protected final boolean tryRelease ( int releases) {
if ( ! isHeldExclusively ( ) )
throw new IllegalMonitorStateException ( ) ;
int nextc = getState ( ) - releases;
boolean free = exclusiveCount ( nextc) == 0 ;
if ( free)
setExclusiveOwnerThread ( null ) ;
setState ( nextc) ;
return free;
}