【JUC源码专题】Semaphore 核心源码分析(JDK8)

semaphore | BrE ˈsɛməfɔː, AmE ˈsɛməˌfɔr |
A.noun uncountable
(using arms) 臂板信号 bìbǎn xìnhào ; (using flags) 旗语 qíyǔ
B.transitive verb
(using arms) 用臂板信号发出 yòng bìbǎn xìnhào fāchū ; (using flags) 用旗语发出 yòng qíyǔ fāchū

信号量就是带有数量的共享锁。

用法

import java.util.concurrent.Semaphore;

public class Test {
    public static void main(String[] args) {
        Semaphore s = new Semaphore(1, true);

        new Thread(()->{
            try {
                s.acquire();
                System.out.println("T1 start...");
                Thread.sleep(200);
                System.out.println("T1 end...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                s.release();
            }
        }).start();

        new Thread(()->{
            try {
                s.acquire();
                System.out.println("T2 start...");
                Thread.sleep(200);
                System.out.println("T2 end...");
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

输出:

T1 start...
T1 end...
T2 start...
T2 end...

源码

构造方法

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

Sync

Semaphore 内部类 Sync 继承了 AQS,同时有 FairSync 和 NonfairSync 2 个子类。这个结构和 [[ReentrantLock]] 很像。

    abstract static class Sync extends AbstractQueuedSynchronizer {
        // 非公平锁获取共享锁的实现
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
        
        // 释放信号量
        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;
            }
        }
    }

acquire()

该方法调用了 AQS 的 acquireSharedInterruptibly 方法。acquireSharedInterruptibly 再调用 Sync 的 tryAcquireShared 方法(这里分为公平和非公平两种实现)。

    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

FairSync#tryAcquireShared

tryAcquireShared 的公平锁实现。

protected int tryAcquireShared(int acquires) {
    for (;;) {
        if (hasQueuedPredecessors()) // 若队列里已经线程在排队,那么直接返回-1
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

NonfairSync#tryAcquireShared

直接调用 Sync 类的 nonfairTryAcquireShared 方法

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }

nonfairTryAcquireShared

nonfairTryAcquireShared 返回值小于 0 表示没获取到信号量。

        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 || // 没有可用资源
                    compareAndSetState(available, remaining)) // 更新可用资源数
                    return remaining;
            }
        }

release

release 调用 AQS 的 releaseShared。然后调用 Sync 的 tryReleaseShared 方法。

    public void release() {
        sync.releaseShared(1);
    }

tryReleaseShared 释放信号量

tryReleaseShared 方法只要执行完成,那么就已经将信号量放入 state 变量中。

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

总结

在这里插入图片描述

伪唤醒问题

当结点状态为 -1 时,就算没有多余的信号量,也会唤醒后继结点。后继结点自行判断是否需要继续阻塞。
在这里插入图片描述

Node.PROPAGATE 问题

目前没有找到具体场景表明必须要设置结点 ws 为 Node.PROPAGATE 才能避免同时有多个线程释放信号量,导致信号量空闲,资源浪费。

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

重点关注这行代码:

else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
        continue;   

论据

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) { // 满足 h.waitStatus < 0
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

若结点的状态为 -1,那么该结点满足 setHeadAndPropagate 中的条件判断,会唤醒下个结点。不管有多少空闲的信号量,都会链式唤醒后继结点。
若结点状态为 0,则表明后继结点还未 park(后继结点 park 之前必会将前置结点的 ws 改为 -1),等到当前结点是头结点时,后继结点就会尝试获取锁。
上面两种情况已经保证了只要有空闲的信号量,就会被线程获取,不存在资源浪费的情况。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值