锁系
非公平 可重入锁源码步骤
if state.cas(0, 1)
上来先<strong>尝试cas抢锁</strong>. 如果state设置为1了 那么就算是抢到了,设置当前加锁线程为自己,方法结束
else
acquire(1) -> tryAcquire(1) -> nonfairTryAcquire(1)
1. 获取state.
if state==0 <strong>尝试cas抢锁</strong>, 抢成功了 return true
else 当前线程==现持锁线程 state++ return true
return false
以上如果因为非公平而抢到了锁, 这里方法无阻塞结束,加锁成功
if !tryAcquire(1) && acquireQueued(addWaiter(Node.EXCLUSIVE), 1)) selfInterrupt() //Node.EXCLUSIVE==null
否则走 acquireQueued..
AddWaiter: {
1. new一个node,当前线程指向自己,nextWaiter指向空
2. 如果队列不为空,将该node加到队尾。 //说是队列,其实是由node组成的双向链表结构, node 又prev 和 next指针
3. 如果队列为空,走enq方法:new一个空node放到队头,让上面新node挂在后面
//这个方法主要就是讲 当前没获得所得线程Node加到等待队列末尾,如果等待队列为空,那么构造一个空队头,在入队。
//注意 队头第一个node永远都是空node
}
acquireQueued: {
interrupt = false;
for(;;) {
p = 前一个node
if p==head && tryAcquire(1) { //下面shouldParkAfterFailedAcquire一般第一次都会返回false 所以一般这里会出现两次抢锁
//如果当前队列里,此线程排再第二位, 且 <strong>尝试cas抢锁</strong> 成功
释放头节点,把当前node作为头,并把thread信息 prev 都指为null
return interrupt //return出去 上层方法都没有阻塞 就会返回 -> 加锁成功
}
if(shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
// shouldParkAfterFailedAcquire 这个方法就是讲前面一个Node的waitStatus是不是SIGNAL,如果是return true; 如果不是 cas改为SIGNAL,return false, 进入下一次循环
//parkAndCheckInterrupt 这个方法 调用了 LockSupport.part(this) 会把当前线程挂起在这里. 等待别人唤醒。 然后return Thread.interrupted()
interrupt = true
}
//这个死循环会一直卡到当前线程变为队列中 第二个节点 才会return
}
}
非公平可重入锁释放锁
如果当前线程不是持有锁的线程 throw 异常
如果时当前线程,且state减为0 设置当前加锁线程=null
如果可重入锁 没彻底释放锁 就return false 方法结束
如果可重入锁 彻底释放 unparkSuccessor:
将第一个node 的状态cas改为0
然后LockSupport.unpark(head.next.thread) 将队列中第二个线程唤醒
return true 方法结束
公平锁加锁逻辑总结
每次去抢锁的时候 都要去看看队列。 队列没有元素?可以尝试cas加锁 队列第二个元素是当前线程? 可以尝试加锁 否则都不可以加锁,要去入队
读写锁State解释:
JDK很巧妙的用state一个值表达了 读锁写锁两种锁的区分,且能表达出两种锁各加了多少次。
高16位是读锁,低16位是写锁。
假设 state 二进制位: 0000 0000 0001 0000 0000 0000 0011
写锁数量:state & (1
读锁数量:state >>> 16 也即取高16位的数字
读写锁之写锁加锁
w = 写锁数量 //取state低16位计算写锁数量w
if state != 0 {
//能进来说明 有人加锁
if w == 0 || 当前线程!=当前加锁线程 return false
if 写锁数量超过2^16 抛出异常
//到这里 w!=0 且 是当前加锁线程加的锁
//这里写锁重入
修改state, 加锁成功 返回true
}
公平锁:看看队列有没有node排队,有人排队返回false加锁失败 ,上层代码做需要入队操作(同普通锁)
没人排队或者非公平锁: cas改state 改失败 返回false
设置当前线程为持有锁的线程 return true
=====
以上返回false的 都需要封装node然后入队,然后挂起线程等等 参考普通锁
读写锁之写锁释放锁
tryRelease(释放数量arg) {
if 当前线程!= 持有锁的线程 抛出异常
nextc = state - arg; 释放后state该为多少
free = 写锁释放完后还有写锁数量 == 0?
if free setExclusiveOwnerThread(null) 设置持有写锁的线程为null
修改state=nextc //会有读锁吗??应该不会吧。。
return free
}
if tryRelease {
唤醒队头node线程 return true
}
return false
读写锁之读锁加锁
tryAcquireShared(arg) {
if 有写锁 且 加写锁的线程不是自己 return -1
r = 读锁数量 // 取state高16位
}
读写锁之读释放锁
//
线程控制系
Condtion
condition.await():
addCondtionWaiter() //new Node 加入condition等待队列队尾 (此队列与AQS的队列是两条不同的队列, conditionObject 维护着firstWaiter和lastWaiter两个指针)
fullyRelease() //释放当前持有的锁,并唤醒AQS等待队列
将自己挂起
condition.signalAll():
遍历condition队列,每个node依次调用enq方法加入AQS队列尾部,并把每个node中的线程挂起,参与竞争锁
conditon.signal():
condition队头出队并加到AQS队尾
CountdownLatch
实现原理:AQS的Sync
效果:main线程 latch = new CountDownLatch(3), 主线程调用latch.await()然后卡死,三个线程必须都执行latch.countDown(), 主线程才会被唤醒。
原理:state设置为3, latch.await()方法发现state!=0, 挂起。 latch.countDown()就去做--state操作,当发现state==0 唤醒队列里的休眠线程也即main线程
类比 thread1.join, 等thread1执行完 主线程才能开始执行
countDownLatch = new CountDownLatch(3) ==> 构造函数 new Sync(count) ==> state=count
countDownLatch.await():
if(state == 0) 方法结束
addWaiter(Node.SHARED)
for(;;):
if(node.predecessor == head): 再次判断一遍state是否等于0 等于0return
else: 当前线程挂起
countDownLatch.signal():
CAS做state--
if(state==0): 唤醒AQS等待队列中的第一个元素; return
return
CyclicBarrier
实现原理:使用lock+condition
效果:如果 main线程执行 new CyclicBarrier(3, runnable ), 那么必须等三个线程调用 cyclicBarrier.await都执行(然后卡死在await这里),main线程的runnable才能被执行,然后 三个线程恢复执行
原理:三个线程调用await() 会去
1. 调用lock.lock(),
2. 调用condition.await(), 把锁释放加入等待队列中去。
3. 计算count=--3 是否等于0, 如果等于0 执行runnable中的run方法。 接着condition.signalAll 唤醒 condition队列中另外两个线程去竞争锁
4. finally 释放锁
源码参考原理,基本上全是AQS那一套
Semaphore
new Semaphore(0) state初始等于0
main线程调用semaphore.acquire(1), 每次子线程 谁先调用 semaphore.release 都会把state++ 然后唤醒main线程尝试 acquireShared
Exchange
基本上不用。。。。。
ThreadPoolExecutor
实现了AQS