Java多线程篇(7)——AQS之共享锁(Semaphore、CountDownLatch)

文章目录

1、Semaphore

1.1、acquire

Semaphore.acquire

java 复制代码
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

AbstractQueuedSynchronizer.acquireSharedInterruptibly

java 复制代码
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //尝试获取共享资源(默认非公平的实现)    
        if (tryAcquireShared(arg) < 0)
            //若获取资源失败进入这个方法
            doAcquireSharedInterruptibly(arg);
    }

Semaphore.tryAcquireShared

java 复制代码
   //Semaphore非公平锁的实现
   protected int tryAcquireShared(int acquires) {
       return nonfairTryAcquireShared(acquires);
   }
   final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
           int available = getState();
           int remaining = available - acquires;
           if (remaining < 0 ||
             compareAndSetState(available, remaining))
             return remaining;
          }
    }

	//Semaphore公平锁的实现
    protected int tryAcquireShared(int acquires) {
       for (;;) {
            if (hasQueuedPredecessors())
               return -1;
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
               compareAndSetState(available, remaining))
               return remaining;
        }
    }

AbstractQueuedSynchronizer.doAcquireSharedInterruptibly

java 复制代码
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //封装共享节点并入队
        final Node node = addWaiter(Node.SHARED);
        try {
            for (;;) {
                final Node p = node.predecessor();
                //如果前一个节点是头结点,再次尝试获锁
                if (p == head) {
                    //获取共享资源
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        //如果获取成功设置头节点并传播
                        //其实就是成功获取资源后接着唤醒后面的节点,因为是共享资源,所以要尽可能唤醒更多的节点
                        setHeadAndPropagate(node, r);
                        p.next = null;
                        return;
                    }
                }
                //如果获锁失败或者前节点不是head的节点就根据前节点的状态来看是否需要阻塞
                //需要阻塞就调用 LockSupport.park() 阻塞线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

被唤醒后...
AbstractQueuedSynchronizer.setHeadAndPropagate

java 复制代码
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head;
        //设置头结点
        setHead(node);

		//唤醒后继节点
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //如果是共享节点调用doReleaseShared唤醒后继节点
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

AbstractQueuedSynchronizer.doReleaseShared

java 复制代码
 private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                //cas(SIGNAL->0) + unpark 
                if (ws == Node.SIGNAL) {
                    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                        continue;
                    unparkSuccessor(h);
                }
                //cas(0->PROPAGATE)
                else if (ws == 0 &&
                         !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                    continue;
            }
            //如果head不变,说明已经没有需要唤醒的节点了,则break
            if (h == head)
                break;
        }
    }

1.2、release

Semaphore.release

java 复制代码
    public void release() {
        sync.releaseShared(1);
    }

AbstractQueuedSynchronizer.releaseShared

java 复制代码
    public final boolean releaseShared(int arg) {
        //释放共享资源
        if (tryReleaseShared(arg)) {
            //成功释放则调用 doReleaseShared 唤醒节点
            doReleaseShared();
            return true;
        }
        return false;
    }

Semaphore.tryReleaseShared

java 复制代码
	protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            int current = getState();
            int next = current + releases;
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            if (compareAndSetState(current, next))
                return true;
        }
    }

可以发现独占锁只会唤醒一个节点,而共享锁会类似传播一样从第一个被唤醒的节点开始逐次唤醒后面的节点。

2、CountDownLatch

2.1、await

CountDownLatch.await

java 复制代码
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

acquireSharedInterruptibly上面已经分析过了,这里直接看到CountDownLatch的获取共享资源的实现
CountDownLatch.tryAcquireShared

java 复制代码
   protected int tryAcquireShared(int acquires) {
        //如果state为0,则允许获取,反之不允许
        return (getState() == 0) ? 1 : -1;
   }

2.2、countDown

CountDownLatch.countDown

java 复制代码
    public void countDown() {
        sync.releaseShared(1);
    }

releaseShared上面也已经分析过了,这里同样直接看到CountDownLatch的释放共享资源的实现
CountDownLatch.tryReleaseShared

java 复制代码
	protected boolean tryReleaseShared(int releases) {
       for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            // state - 1
            int nextc = c - 1;
            //cas替换state
            if (compareAndSetState(c, nextc))
                //如果替换后state为0,则返回true,表示可以唤醒节点了
                return nextc == 0;
        }
    }
相关推荐
逝水无殇1 小时前
C# 异常处理详解
开发语言·后端·c#
懒鸟一枚2 小时前
深入理解 Linux 内存、Swap 交换分区与分页机制的关系
java·linux·数据库
玖玥拾3 小时前
C# 语言进阶(十五)C# 游戏服务端 MySQL 数据库
服务器·开发语言·网络·数据库·mysql·c#
铅笔侠_小龙虾3 小时前
Rust 学习目录
开发语言·学习·rust
我命由我123454 小时前
执行 Gradle 指令报错,无法将“grep”项识别为 cmdlet、函数、脚本文件或可运行程序的名称
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
考虑考虑4 小时前
Sentinel安装
java·后端·微服务
云泽8084 小时前
从零吃透 C++ 异常:抛出捕获、栈展开、异常重抛与编码规范详解
开发语言·c++·代码规范
灯澜忆梦4 小时前
GO---可见性规则
开发语言·golang
逝水无殇4 小时前
C# 文件的输入与输出详解
开发语言·数据库·后端·c#
这是个栗子5 小时前
前端开发中的常用工具函数(八)
开发语言·前端·javascript