AQS 巨好理解的版本

AQS

同步队列(入口等待队列):(ReentrantLock、ReentrantReadWrite)

用于实现 独占锁,例如 ReentrantLock、ReentrantReadWrite

首先,CAS尝试加锁。

加锁成功:设置 state=1(原来state=0),然后设置独占属性 exclusiveOwnerThread = thread()(用于可重入锁判断)

加锁失败:只能干什么两件事:

  • 入队:如果你没有队列,还得构建一个队列,然后完成入队操作,后续再有加锁失败,用尾插法插入队列中。

  • 阻塞:入队之后,才能用 LockSupport.park(this) 阻塞这个入队的线程

后面加锁的线程完全了业务,然后解锁

解锁:将需要解锁的线程 state设置为0 ,然后再设置独占属性 exclusiveOwnerThread = null

然后唤醒阻塞队列中的线程 (只有head节点后面一个节点才能被唤醒),并将这个节点出队。

等待队列(条件队列):

用于实现 共享锁,例如 Semaphore、CountDownLatch、CyclicBarrier

前半段:await进入 条件队列 并 释放锁,然后构建队列(单向链表),入队之后阻塞线程(LockSupport.park)。

过渡阶段:被其他调用singal/singalAll的线程唤醒(前提要在同步队列中)

调用singal/singalAll的线程:条件队列转同步队列,可以在释放锁的时候唤醒head的后续节点所在的线程。

后半段:获取锁(如果有竞争,cas获取锁失败,还会阻塞),

释放锁(唤醒同步队列中head的后续节点所在的线程)

后半段的逻辑其实就是独占锁的逻辑。

ReentrantLock(独占锁、可重锁):

AQS的实现,独占锁,可重入锁。

可重入:利用 state 来计数,每当本线程用 同一个 ReentrantLock 时,state 就 +1,释放锁是挨个 -1,只有当 state 减少为0,才会完全释放。

流程:

当一个线程获取 ReentrantLock 锁时,它会尝试获取锁状态,如果 state =0(空闲状态),则此线程成功获取锁,将 state设置为1。

如果同一个再次获取这个 ReentrantLock 锁,检查是否持有这个锁,如果是state就会加1。释放锁的时候,每释放一次 state 就会 -1,当 state为0时,才会完全释放该锁。

Semaphore(共享锁):

俗称信号量,是os的pv的语的java中的实现,AQS的实现,共享锁

共享锁:例如 new Semaphore(3) 表示许可数为3,同时最多可有三个线程访问资源,semaphore可以控制并发访问共享资源的线程数量

流程

当有线程 semaphore.acquire(1) ,阻塞获取许可。

获取成功 (state>0),许可数-1(state-1) 并执行业务代码。

获取失败(state=0),就跟ReentrantLock的一样,入队和阻塞:

  • 入队:没有队列就构建一个队列,然后尾插法入队。

  • 阻塞:入队之后,就 LockSupport.park() 阻塞

当 获取锁的线程 执行业务,semaphore.release(1) 释放许可,许可数+1(state+1)

然后,唤醒阻塞队列中的head后面的那一个节点,然后再 许可数-1

CountDownLatch:

通过AQS的 "共享锁" 实现

构造方法:
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
重要方法:
// 调用 await() 方法的线程会被挂起,它会等待直到 count 值为 0 才继续执行
public void await() throws InterruptedException { };  
// 和 await() 类似,若等待 timeout 时长后,count 值还是没有变为 0,不再等待,继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
// 会将 count 减 1,直至为 0
public void countDown(){ };
使用场景:

CountDownLatch一般用作多线程倒计时计数器,强制它们等待其他一组(CountDownLatch的初始化决定)任务执行完成。

CountDownLatch的两种使用场景:

  • 场景1:让多个线程等待

  • 场景2:让单个线程等待。

CyclicBarrier(循环屏障)

ReentrantLock的"独占锁"+Condition来实现条件队列的阻塞与唤醒

构造方法:
// parties表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
public CyclicBarrier(int parties)
// 用于在线程到达屏障时,优先执行 barrierAction,方便处理更复杂的业务场景(该线程的执行时机是在到达屏障之后再执行)
public CyclicBarrier(int parties, Runnable barrierAction)
重要方法:
//屏障 指定数量的线程全部调用await()方法时,这些线程不再阻塞
// BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
​
//循环  通过reset()方法可以进行重置
//reset() 方法会将当前记录的线程清除。当调用 reset() 方法时,无论当前线程是否已经到达栅栏,所有已经到达栅栏但还在等待的线程都会被中断(抛出 BrokenBarrierException 异常),并且栅栏会重置为初始状态。
使用:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
​
public class CyclicBarrierExample {
    private static final int THREAD_COUNT = 3;
    private static final CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> {
        System.out.println("All threads reached the barrier. Continuing...");
    });
​
    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            Thread thread = new Thread(new WorkerThread());
            thread.start();
        }
    }
​
    static class WorkerThread implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println("Thread " + Thread.currentThread().getId() + " is performing some work.");
                // 模拟线程执行一些工作
                Thread.sleep(2000);
                System.out.println("Thread " + Thread.currentThread().getId() + " has reached the barrier.");
                barrier.await(); // 线程到达栅栏,等待其他线程
                System.out.println("Thread " + Thread.currentThread().getId() + " continues execution after the barrier.");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

输出:

Thread 10 is performing some work.
Thread 12 is performing some work.
Thread 11 is performing some work.
Thread 10 has reached the barrier.
Thread 12 has reached the barrier.
Thread 11 has reached the barrier.
All threads reached the barrier. Continuing...
Thread 12 continues execution after the barrier.
Thread 10 continues execution after the barrier.
Thread 11 continues execution after the barrier.
CyclicBarrier为什么要用ReentranLock?
  • 独占锁:CyclicBarrier需要确保同一时刻只有一个线程能够更新内部计数器和状态。

  • 可重入性:ReentrantLock支持线程重复获取锁。对于CyclicBarrier来说是必要的,因为同一线程可能会多次参与到栅栏的等待和唤醒。

还有一个,因为Condition的await方法释放锁,而你没有锁怎么释放锁,前面要加锁lock.lock()

ReentrantReadWriteLock(读写锁):

读写锁,读锁是AQS的共享锁,写锁是AQS的独占锁,适用于读多写少的场景

构造方法:
    //无参数,默认是不公平
    public ReentrantReadWriteLock() {
        this(false);
    }
​
    //有参数,可设置是否公平
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

使用:
import java.util.concurrent.locks.ReentrantReadWriteLock;
​
public class ReadWriteLockExample {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    private int sharedData = 0;
​
    public int readData() {
        readLock.lock();
        try {
            // 读取共享数据
            return sharedData;
        } finally {
            readLock.unlock();
        }
    }
​
    public void writeData(int data) {
        writeLock.lock();
        try {
            // 写入共享数据
            sharedData = data;
        } finally {
            writeLock.unlock();
        }
    }
}

注意事项
  • 读锁不支持条件变量

  • 重入时升级不支持:持有读锁的情况下去获取写锁,会导致获取永久等待

  • 重入时支持降级: 持有写锁的情况下可以去获取读锁

ReentrantReadWriteLock 没有像 Semaphore 一样指定共享锁允许的数量,默认是多少个?

源码中:

static final int SHARED_SHIFT   = 16;
/** Returns the number of shared holds represented in count  */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

可以知道默认同时共享资源的线程为16个。

扩展waitStatus字段:

  • CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。

  • SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。

  • CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。

  • PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。

    0:新结点入队时的默认状态。

    注意,负值表示结点处于有效等待状态,而正值表示结点已被取消。所以源码中很多地方用>0、<0来判断结点的状态是否正常

觉得文章不错,给个关注呗!嘻嘻

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值