共享式AQS

共享式AQS

阻塞式锁,在同一时刻只能有一个线程在执行,当一个线程执行完成后,再去释放下一个线程,而共享式是指,锁是可以被共享的,表现形式为,在同一时刻可以有多个线程运行。

通过源码分析共享式AQS的实现

CountDownLatch、Semaphore都属于共享锁。基于网上有好多博客都是分析CountDownLatch,所以我在此处分析一下Semaphore。

Semaphore的基本使用
public class SemaphoreTest {
        public static void main(String[] args) {
            Semaphore semaphore = new Semaphore(3);
            for (int i = 0; i < 10; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            semaphore.acquire();//获取锁
                            System.out.println(Thread.currentThread().getName() + " " + new Date() + " 我执行了");
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }finally {
                            semaphore.release();//释放锁
                        }
                    }
                }).start();
            }
        }
    }

上面的代码很简单,初始化一个可容纳3个线程的信号量,初始化10个线程,每个线程执行一次打印(延迟1秒)。
以上代码运行结果:

和排它锁相比,同一时刻允许多个线程同时执行。
看下Semaphore的内部类结构

从类结构上可以看出,Semaphore也是支持公平锁和非公平锁的实现,其锁的实现同样是通过Sync集成AQS实现。

Semaphore的acquire方法
public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

acquire sync通过调用AQS的acquireSharedInterruptibly方法实现

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())//首先判断线程是否被终端
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)//尝试获取锁,获取不到则执行下面方法,小于0代表获取不到锁,大于0代表获取成功,0代表成功,但没有剩余可用资源
        doAcquireSharedInterruptibly(arg);
}

cquireSharedInterruptibly名字可以看出这个方法是可相应中断的共享式获取锁。
tryAcquireShared肯定也是一个空方法,留给子类实现。

protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }

看下Semaphore 公平锁的tryAcquireShared实现

protected int tryAcquireShared(int acquires) {
        for (;;) {//CAS的方式循环获取锁
            if (hasQueuedPredecessors())//队列中有线程在等待,直接返回-1,代表当前没有可用资源
                return -1;
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))//要么没有可用资源,要么当前资源CAS操作成功,则返回可用的资源数
                return remaining;
        }
    }

当没有可用的资源时,执行doAcquireSharedInterruptibly方法

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);//保证节点一定会被插入到队尾。
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//操作流程和排它锁一样
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

此时的数据结构如下:

主要看下setHeadAndPropagate方法,这个方法是在线程被唤醒之后要执行的

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())//节点为空(下面没有等待的线程)或者下一个节点依然是共享模式,那么就执从头结点开始执行唤醒
                doReleaseShared();
        }
    }

这种唤醒方式和排它锁的区别为,共享模式,每一次都是从头结点开始,唤醒一个线程,紧接着就会去试图唤醒下一个节点的线程,只要有足够的资源,线程就会被唤醒,就这样循环往复。而不像排它锁一样,要等待一个节点执行完成,再去唤醒下一个节点。

Semaphore的release方法
public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {//保证锁一定被释放
            doReleaseShared();//开启释放流程
            return true;
        }
        return false;
    }
共享锁总结
  1. 共享锁允许统一时刻有多个线程运行。
  2. 锁的释放过程和排他锁相比,具有传播性,一个节点被唤起,则会从头节点开始一个一个尝试唤醒。
### Java AQS (AbstractQueuedSynchronizer) 使用指南 #### AQS简介 AQS 是 Java 并发包中的核心组件之一,提供了一种用来构建锁和其他同步器的基础框架。该类位于 `java.util.concurrent.locks` 包下,并且实现了 FIFO 队列来管理线程等待队列[^1]。 #### 继承结构 AQS 和 AQLS (AbstractQueuedLongSynchronizer)都继承了名为 AOS(AbstractOwnableSynchronizer)的一个抽象类。此父类引入于 JDK 1.6 版本中,主要用于表示锁与其拥有者间的关系,在独占模式下调用方可以设置当前占有锁的对象实例。 #### 设计灵活性 为了适应不同的应用场景需求,AQS 提供了一系列模板方法让开发者能够方便地创建自定义同步工具。这些方法包括但不限于: - **tryAcquire(int arg)**:尝试获取独占式的资源; - **tryRelease(int arg)** :尝试释放已持有的独占式资源; - **tryAcquireShared(int arg)** : 尝试获得共享类型的许可; - **tryReleaseShared(int arg)** : 放弃之前得到过的共享权限; 上述四个函数都需要由具体的子类去重写实现,从而定义各自独特的同步机制[^3]。 #### 实际应用案例 - CountDownLatch 作为 AQS 应用的经典范例之一,`CountDownLatch` 展现了如何利用 AQS 来处理多线程间的协作问题。此类允许一个或多个线程一直阻塞直到其他一些线程完成一系列操作之后再继续执行下去。下面给出一段简单的代码片段展示其基本用法: ```java import java.util.concurrent.CountDownLatch; public class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; public Worker(CountDownLatch startSignal, CountDownLatch doneSignal){ this.startSignal = startSignal; this.doneSignal = doneSignal; } @Override public void run(){ try{ System.out.println(Thread.currentThread().getName()+" is waiting"); startSignal.await(); // wait until all threads are ready. doWork(); System.out.println(Thread.currentThread().getName()+" has finished work."); doneSignal.countDown();// signal that the current thread has completed its task. }catch(Exception e){ throw new RuntimeException(e); } } protected void doWork(){} } // Usage example: int nThreads = 5; CountDownLatch startGate = new CountDownLatch(nThreads); CountDownLatch endGate = new CountDownLatch(nThreads); for(int i=0;i<nThreads;++i){ Thread t=new Thread(new Worker(startGate,endGate),"Worker-"+i); t.start(); } startGate.countDown(); // release all workers at once endGate.await(); // main waits here for them to finish their jobs System.out.println("All tasks have been processed!"); ``` 这段程序展示了五个工作线程在接收到启动信号前处于挂起状态,当最后一个工作者准备好后它们会同时开始运行各自的作业并最终通知主线程所有任务已完成[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值