AQS源码浅析

锁系

非公平 可重入锁源码步骤

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的AQS(AbstractQueuedSynchronizer)是实现锁和同步器的一种重要工具。在AQS中,一个节点表示一个线程,依次排列在一个双向队列中,同使用CAS原子操作来保证线程安全。当多个线程对于同一资源竞争,一个节点会被放置在队列的尾部,其他线程则在其之前等待,直到该资源可以被锁定。 当一个线程调用lock()方法进行锁定,它会首先调用tryAcquire()方法尝试获取锁。如果当前资源尚未被锁定,则该线程成功获取锁,tryAcquire()返回true。如果当前资源已被锁定,则线程无法获取锁,tryAcquire()返回false。此该线程就会被加入到等待队列中,同被加入到前一个节点的后置节点中,即成为它的后继。然后该线程会在park()方法处等待,直到前一个节点释放了锁,再重新尝试获取锁。 在AQS中,当一个节点即将释放锁,它会调用tryRelease()方法来释放锁,并唤醒后置节点以重试获取锁。如果当前节点没有后置节点,则不会发生任何操作。当一个线程在队列头部成功获取锁和资源,该线程需要使用release()方法释放锁和资源,并唤醒等待队列中的后置节点。 总之,AQS中的锁机制是通过双向等待队列实现的,其中节点表示线程,使用CAS原子操作保证线程安全,并在tryAcquire()和tryRelease()方法中进行锁定和释放。该机制保证了多线程环境下资源的正确访问和线程的安全执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值