java-Lock
2019-07-24 17:31:13 0 举报
JDK JUC 锁分析
作者其他创作
大纲/内容
next =nullwaitStatus=0
④
lastWaiter
head
读锁逻辑代码lock.readLock().lock()
t3将前一个节点t2.waitStatus置为-1(当前节点将前一个置为-1)
firstWaiter
t4(卡在①主)
public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); }//读锁和写锁都用Sync protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync; } protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; }
waitStatus=0
⑤
③
t4(park卡住,需要被唤醒)
waitStatus=-1
protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error(\"Maximum lock count exceeded\
lock#lock()方法时序图
t1(卡在②主)
t3(卡在②主)
next =nullwaitStatus=-2
单向链表firstWaiter和nextWaiter之间用nextWaiter连接
next =nullwaitStatus=0在变成-1
条件锁
t3(被t2唤醒 自旋tryAcquire一次,抢不到在park住)
锁
场景假设B) 移除操作 take操作:t5获取到lock的锁(state)经过③,进入到⑤处
AQS.ConditionObject
t2(死循环tryAcquire,没有park住)
卡主
t2(卡在①主)
protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)font color=\"#cc0000\
t2获取锁,执行业务逻辑,释放锁时候,将head.next即t3唤醒
AQS
AQS#singal
ReentrantLock
acquire
场景假设:A)添加操作Object[] item容器已满条件下: put操作: t1 成功获取到锁,所以AQS.state=1,但是已满,t1进入到②出,进入条件单向链表 t2 由于t1获取到锁,进入双向队列排队 t3 由于t1j进入到condition.await()方法会先释放锁即AQS.state=0 (如上代码), 所以t3可以获取进入到t1所在的条件单向链表
lock方法即①处导致的排队
t4将前一个节点t3.waitStatus置为-1(当前节点将前一个置为-1)
t2
AQS#await
t3(park卡住,需要被唤醒)
②
2)插入末尾
nextwaitStatus=-2
t2(自旋tryAcquire 一下font color=\"#ff0000\
waitStatus=-2
读是共享的
lock
补充
acquireQueue 进入双向链表
lock方法创建的条件
双向链表head和tail之间用prev和和next连接
第五步:线程t2成功获取到了锁,要进入判断容器是否满,如果满了则进入条件队列
移除操作
t2(不在卡主进入判断满了了进入条件队列)注意是移动过来的,不是新创建的
用法
t2(节点变成head)
tail
作用:看似多次依据,其实是锁降级,这一行导致了,在获取写锁的,未释放写锁时,有获取读锁成功!!!首先,什么是锁降级?【出现在获取读锁的时候】引自 JDK 的解释:重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。锁降级必要性:可见性如果当前线程在写锁中更新了变量,先释放写锁,再加读锁读取,另个一个线程t有可能获取写锁更新数据,而当前线程感知不到,就悲剧了如果当前线程在写锁中获取读锁,由于锁遵循锁降级:读锁读取,等读锁释放掉之后,此时其它线程写线程才可以写
逻辑比较简单:c!=0 要么是读锁,要么是写锁 是写锁 锁持有者不是当前线程获取锁失败 锁持有者是当前线程则可以重入,判断重入次数哈c=0 支持cas一次 成就成 败就败
在ReentrantLock中1)获取锁成功,实际就是当前线程将AQS.state通过cas设置为12)重入锁 当前线程已经获取到锁的情况下(state=1)再次进行cas设置state+1操作3)公平锁 抢锁的时候直接加入到双向链表的等待队列 添加自己到末尾,然后在进行自旋(实际自旋一次后park挂起了,只有head的next在死循环traAcquire)4)非公平锁 在获取锁时候跟等待队列里的head.next都在tryAcquire(跟非公平锁的区别是上来就先跟head.next抢一下)5)lock()没有带有等待时间的方法 tryLock才有等待时间等待时间>1000纳秒(1ms) 5.1)调用LockSupport.parkNanos(time) 5.2)小于的话,不会调用它而是死循环为自己的前一个是不是头, 是的话尝试获取, 不是的话在此询问6) acquireQueue自旋 实际上只有head.next 尝试获取锁一下(就一下),然后执行shouldParkAfterFailedAcquire,跟其它节点一样将prev.waitStatus=-1在park住,等待被唤醒
Node
- prev:Node- next:Node- waitStatus:int- thread:Thread- nextWaiter:Node
两处跟ReentrantLock入队和尝试获取锁的不同之处
addWaiter(Node.EXECULSIVE)
t1进入条件队列(释放锁),导致t3调用put也能获取锁成功,但是容器已满span style=\"font-size: inherit;\
Sync
waitStatus=0或者-1
第三步:signal方法doSignal操作2transferForSignal移动到双向队列
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
逻辑也比较简单:判断是否有写锁(高位的值大于0) 有返回-1 入队(后边讲) 无 直接cas设置state cas设置成功:直接记录当前线程增加的holder.count cas设置失败:死循环设置fullTryAcquireShared
waitStatus有可能是-1(singal)也有可能是0 ,关键看t2自旋 tryAcquire拿一下能否成功
第四步:如果t1被唤醒t1将执行acquireQueued
ReentrantLock-条件锁
假设线t1执行lock()方法成功获取到锁(state=1了),线程t2和线程t3获取锁失败进入双向队列 那么t2和t3入队的操作可能是如下一种情况t2入队操作如下:1)构建一个空Node当做head,2)将t2放到head之后最为tailt3直接入队成为队尾
第一步:signal方法doSignal操作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; }
t1释放锁,t2获取锁,t2变成head,t1因为没有在队列中,t2为park住所以不用唤醒其它的线程
AbstractQueuedSynchronizer
awaiter方法进入双向链表
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
共享锁(读)是state的高16位
final int fullyRelease(Node node) { boolean failed = true; try { int savedState = getState(); if (release(savedState)) { failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) node.waitStatus = Node.CANCELLED; } }
AQS.Node
一个是 firstReader和 firstReaderHoldCount 的作用,还有就是 cachedHoldCounter 的作用。解释一下:firstReader 是获取读锁的第一个线程。如果只有一个线程获取读锁,很明显,使用这样一个变量速度更快。firstReaderHoldCount是 firstReader的计数器。同上。cachedHoldCounter是最后一个获取到读锁的线程计数器,每当有新的线程获取到读锁,这个变量都会更新。这个变量的目的是:当最后一个获取读锁的线程重复获取读锁,或者释放读锁,就会直接使用这个变量,速度更快,相当于缓存。
独占锁(写)是state的低16位
读线程是可以多次持有读锁的
head.next尝试获取锁
await方法即①处导致的排队
写锁逻辑代码lock.writeLock().lock()
t1释放锁,就是span style=\"font-size: inherit;\
等待节点以及队列类图
共享锁
用法典型的生产者和消费者模型
t3
acquireQueued
ReentrantLock-读写锁,锁降级
锁释放
第二步:signal方法doSignal操作2transferForSignal条件节点-2变为0
①
- head:Node- tail:Node- state:int
public final void signal() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignal(first); }
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; }
ReentrantReadWriteLock
- writeLock: ReentrantReadWriteLock.WriteLock- readLock: ReentrantReadWriteLock.ReadLock- sync:Sync
添加操作
1)构建空Node
private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); }
ConditionObject
- firstWaiter:Node- lastWaiter:Node
0 条评论
回复 删除
下一页