多线程第十旅

本文详细解析了AQS(AbstractQueuedSynchronizer)的工作原理,包括独占锁与共享锁的获取与释放流程,以及同步队列的管理机制。AQS是Java并发包中的核心组件,用于构建各种同步器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

8.4:AQS提供的模板方法

AQS使用模板模式,将一些与状态相关的核心方法开放给子类重写,而后AQS会使用子类重写的关于状态的方法进行线程的排队,阻塞以及唤醒等操作

在同步组件中,AQS是最核心的部分,同步组件的实现依赖AQS提供的模板方法来实现同步组件语义。

AQS实现了对同步状态的管理,以及对阻塞线程进行排队,等待通知等底层实现

1.独占锁:

1. void acquire(int arg) : 独占式获取同步状态,如果获取失败则将当前线程插入同步队列进行等待。

2. void acquireInterruptibly(int arg) : 与acquire方法相同,在1的基础上增加响应中断。

3. boolean tryAcquireNanos(int arg,long nanosTimeout) : 在2的基础上增加了超时等待功能,在规定时间内没有获得同步状态返回false

4. boolean tryAcquire(int arg) : 获取锁成功返回true,否则返回false

5. boolean release(int arg) : 释放同步状态,该方法会唤醒在同步队列中的下一个节点。

2.共享式锁:(比独占锁多了一个share

1. void acquireShared(int arg) : 共享式获取同步状态,与独占锁的区别在于同一时刻有多个线程获取同步状态。(忽略模板)

2. void acquireSharedInterruptibly(int arg) : 增加了响应中断的功能

3.boolean tryAcquireSharedNanos(int arg,lone nanosTimeout) : 在2的基础上增加了超时等待功能

4. boolean releaseShared(int arg) : 共享锁释放同步状态。

 

 

 

3.同步队列(获取锁失败的队列)

AQS中的同步队列是一个带有头尾节点的双向链表,节点的组成为

1. int waitStatus; // 节点状态

2. Node prev; // 当前节点的前驱节点

3. Node next; // 当前节点的后继节点

4. Thread thread; // 当前节点所包装的线程对象

5. Node nextWaiter; // 等待队列中的下一个节点

 

将线程封装为Node节点后进行入队与出队处理

 

 

节点的状态如下:

1. int INITIAL = 0; // 初始状态

2. int CANCELLED = 1; // 当前节点从同步队列中取消

3. int SIGNAL = -1; // 后继节点的线程处于阻塞状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程继续运行。

4. int CONDITION = -2; // 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了

signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中。

5. int PROPAGATE = -3; // 表示下一次共享式同步状态获取将会无条件地被传播下去。

 

8.5深入理解AQS

 

8.5.1独占锁

 

独占锁的获取:acquire (int arg)

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

 

  1. tryAcquire再次使用CAS尝试获取同步状态,若成功方法直接返回,当前线程置为持有锁线程,若再次尝试失败,调用addWaiter()

 

    2.addWaiter()源码--将当前线程封装为Node节点后尾插入同步队列

private Node addWaiter(Node mode) {

//将当前线程以指定模式( 独占式或共享式)封装为Node节点
    Node node = new Node(Thread.currentThread(), mode);
    //拿到当前队列的尾节点  

   Node pred = tail;
    if (pred != null) {
        node.prev = pred;

//将当前节点使用CAS尾插入同步队列
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }

//当前队列为空或CAS尾插失败时
    enq(node);
    return node;
}

 

3.enq():当同步队列为空时,完成队列的初始化操作以及不断CAS将当前节点尾插入同步队列

private Node enq(final Node node) {

//死循环,不断自旋
    for (;;) {

//拿到尾节点
        Node t = tail;

//当前节点为空
        if (t == null) {

//完成队列的初始化操作,懒加载模式
            if (compareAndSetHead(new Node()))//头节点,不放数据,只是一个起始位置
                tail = head;
        } else {
            node.prev = t;

//不断将当前节点使用CAS尾插入同步队列中直到成功为止
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

 

4.acquireQueued()

final boolean acquireQueued(final Node node, int arg) {

//设置失败状态,初始化为true
    boolean failed = true;
    try {

//设置中断状态,默认为false;

        boolean interrupted = false;

//不断自旋
        for (;;) {

//拿到当前节点前驱节点
            final Node p = node.predecessor();

//___________________________________

//当前节点的前驱节点为头节点并且再次获取同步状态成功
            if (p == head && tryAcquire(arg)) {

//————————————————————————

//将当前节点置为头节点,并且清空节点中线程,将前驱节点出队
                setHead(node);

//将前驱节点出队
                p.next = null; // help GC


                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {

//将当前节点置为取消状态,node.waitStutas=1;
        if (failed)
            cancelAcquire(node); }}

节点从同步队列获取同步状态的前提条件:

只有当前驱节点为头节点时,线程才有机会获取同步状态

if (p == head && tryAcquire(arg))

 

将当前节点通过setHead()方法设置为队列的头结点,然后将之前的头结点的next域设置为null并且pre域也为null,即 与队列断开,无任何引用方便GC时能够将内存进行回收。

示意图如下:

5.当前线程获取同步状态失败时,首先用shouldParkAfterFailedAcquire(Node pred, Node node)

 

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) //当前节点是否应该被停止{

//获取前驱节点状态
    int ws = pred.waitStatus;

//状态为-1,当前节点的后继节点处于等待状态就是node
    if (ws == Node.SIGNAL)
       //表示应该将当前节点阻塞
        return true;

//前驱节点状态被取消
    if (ws > 0) {
         //一直向前找到节点状态不是取消状态的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {

//将前驱节点状态置为signal,-1
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

 

Pred前驱节点    ws前驱节点状态, Node.SIGNAL需要更新后的值

shouldParkAfterFailedAcquire()尝试将前驱节点状态改为Node.SIGNAL,表示此时当前节点应该被阻塞;否则一直在  

 

6.private final boolean parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt()

{

//将当前线程阻塞
    LockSupport.park(this);
    return Thread.interrupted();
}

 

 

 

独占式锁的获取过程:

独占式锁的释放--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;
}

tryRelease():提供给子类覆写

2.unparkSuccessor():唤醒距离头节点最近的一个非空节点(可以最大可能的保证公平)

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);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值