深入理解AQS底层源码(一)——lock.lock()

前期准备:

1、可重入锁(递归锁):外层使用锁后,在内层仍然可以使用锁。同一个线程可以获取多个同一把锁。

优点:避免死锁。特点:锁对象是同一个对象

2、LockSupport:LockSupport.park(); LockSupport.unPark(Thread t);

类比 synchronized wait notify  ;   lock.newCondition await signal

wait notify必须和synchronized搭配使用且先wait后notify;如果脱离synchronized使用,则报illegalMonitorStateException异常。
await signal必须和lock.newCondition搭配使用且先await后signal;如果脱离lock锁,则报illegalMonitorStateException异常。

LockSupport.park(); LockSupport.unPark(Thread t); 不分前后顺序。

LockSupport使用了一种名为permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个permit,permit只有两个值1和0,默认是0。可以把permit看作是一个信号量(Semaphore),但不同的是,permit的上限是1。

言归正传:

AbstractQueuedSynchronizer(AQS):state+CLH队列 ;node ;head(头)、tail(尾)、prev(前)、next(后)

本次以ReentrantLock的非公平锁为例:

场景:3个线程抢占一个资源,使用lock.lock(); 和 lock.unLock();

第一步:线程1:lock.lock();调用ReentrantLock的lock()方法,底层调用sync.lock();  Sync是继承了AbstractQueuedSynchronizer的ReentrantLock的抽象静态内部类。

NonfairSync继承Sync调用lock方法,通过CAS将state从0 -> 1,锁住当前线程1,线程1开始执行其任务,假设执行20min。

第二步:线程2也想要执行线程1的操作,调用lock方法发现state是1,并不是期望的0,所以调用acquire(1)方法。如上图的else中的方法。

第三步:线程2调用tryAcquire(arg)方法中的nonfairTryAcquire(acquires)方法,返回false;

        current = 线程2;c = 1;getExclusiveOwnerThread() = 线程1;

3.1、if(c == 0),逻辑解释。

        线程1解锁,state置为0,线程2刚好抢到锁,线程2执行加锁操作,state置为1,返回true

3.2、else if(curent == getExclusiveOwnerThread()),逻辑解释

        线程1再次加锁,state置为2,返回true。(这就是可重入锁的概念)

第四步:线程2调用addWaiter(Node.EXCLUSIVE),线程2开始入队:

        线程2 放到 node中,tail(尾指针 == null),执行enq(node)方法。

node是线程2,tail(尾指针 == null)执行compareAndSetHead(new Node());创建一个空的node示例,可以理解为傀儡节点。

(即双向链表的第一个节点不是实际的线程节点而是new的空节点,为了占位),head和tail都指向空节点

for循环第二次,node成为真正的线程2,tail = t 空节点,走else 线程2的前指针指向空节点,tail(尾指针)指向线程2,空节点的后指针指向线程2。

线程2节点入队完毕。

第五步:执行enq(node)方法完毕后,线程2入队成功,返回线程2节点。

线程3执行addWaiter(Node.EXCLUSIVE)方法时,tail = pred != null。tail 是线程2的节点,然后compareAndSetTail(pred, node),tail的指针指向线程3。

线程2的next指向线程3,返回线程3节点,线程3节点入队。

第六步:执行acquireQueued(final Node node, int arg)方法,node是线程2节点,p = head = 空节点。

执行第2个条件tryAcquire(arg),arg = 1. 此时线程1还在占用锁中,所以返回false。

线程1unlock()后,p == head && tryAcquire(arg) == true,head节点设置为线程2节点,原先的head节点的尾节点设为null,被GC回收。

线程1还在锁中时,线程3执行到 acquireQueued(final Node node, int arg)方法时,node = 线程3的节点,arg = 1。

Node p = node.predecessor()  = 线程2节点,head还是空节点。第1个条件返回false,开始执行到shouldParkAfterFailedAcquire(p, node)方法。

一直自旋直至返回true。

finally执行的是取消获取锁的程序。

开始执行shouldParkAfterFailedAcquire(p, node)方法,p = head = 空节点,node是线程2节点。

空节点的waitStatus = ws  = 0; 走到else里面执行 compareAndSetWaitStatus(pred, ws, Node.SIGNAL)方法,把head节点的waitStatus设值为-1且返回false。

线程2节点的waitStatus = ws  = -1且返回true,因为waitStatus时volatile修饰的。

线程2节点执行shouldParkAfterFailedAcquire(p, node)方法,返回true;线程2执行parkAndCheckInterrupt(),线程2park().

线程3执行到shouldParkAfterFailedAcquire(p, node)方法,返回true;p = 线程2节点,node是线程3节点;线程3执行parkAndCheckInterrupt(),线程3park().

以上是加锁lock的执行过程。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值