Semaphore源码解析

目录

内部类同步器Sync

非公平NonfairSync

公平FairSync

代码执行过程


Semaphore,信号量,可以用来控制同时访问特定资源的线程数量,可以将信号量看做是在向外分发使用资源的许可证,只有成功获取许可证,才能使用资源。

底层基于AQS实现。存在Sync、NonfairSync、FairSync三个内部类,Sync类继承自AbstractQueuedSynchronizer抽象类,NonfairSync与FairSync类继承自Sync类。 

两种模式下分别提供了限时/不限时、响应中断/不响应中断的获取资源的方法(限时获取总是及时响应中断的),而所有的释放资源的 release() 操作是统一的。 

主要变量和方法

acquire()  获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
acquire(int permits)  获取多个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
acquireUninterruptibly() 获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。    
tryAcquire() 尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
tryAcquire(long timeout, TimeUnit unit) 尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
release() 释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
​hasQueuedThreads() 等待队列里是否还存在等待线程。
getQueueLength() 获取等待队列里阻塞的线程数。
drainPermits() 清空令牌把可用令牌数置为0,返回清空令牌的数量。
availablePermits() 返回可用的令牌数量。

内部类同步器Sync

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;
    // 赋值setState为总许可数
    Sync(int permits) {
        setState(permits);
    }
    // 剩余许可数
    final int getPermits() {
        return getState();
    }
    // 自旋 + CAS 非公平获取
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            // 剩余可用许可数
            int available = getState();
            // 本次获取许可后,剩余许可
            int remaining = available - acquires;
            // 如果获取后,剩余许可大于0,则CAS更新剩余许可,否则获取更新失败
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
    // 自旋 + CAS 释放许可
    // 由于未对释放许可数做限制,所以可以通过release动态增加许可数量
    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");
            // CAS更新许可数量
            if (compareAndSetState(current, next))
                return true;
        }
    }
    // 自旋 + CAS 减少许可数量
    final void reducePermits(int reductions) {
        for (;;) {
            // 当前剩余许可
            int current = getState();
            // 更新值
            int next = current - reductions;
            // 如果更新值比当前剩余许可大,抛出益处
            if (next > current) // underflow
                throw new Error("Permit count underflow");
            // CAS 更新许可数
            if (compareAndSetState(current, next))
                return;
        }
    }
    // 丢弃所有许可
    final int drainPermits() {
        for (;;) {
            int current = getState();
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}

非公平NonfairSync

重写了AQS的tryAcquireShared方法,调用父类Sync的nonfairTryAcquireShared方法,表示按照非公平策略进行资源的获取。

/**
* 非公平模式
*/
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

    NonfairSync(int permits) {
        super(permits);
    }

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

公平FairSync

重写了AQS的tryAcquireShared方法

/**
* 公平模式
*/
static final class FairSync extends Sync {
    private static final long serialVersionUID = 2014338818796000944L;

    FairSync(int permits) {
        super(permits);
    }
    // 公平模式获取许可
    // 公平模式不论许可是否充足,都会判断同步队列中是否线程在等待,如果有,获取失败,排队阻塞
    protected int tryAcquireShared(int acquires) {
        for (;;) {
            // 如果有线程在排队,立即返回
            if (hasQueuedPredecessors())
                return -1;
            // 自旋 + CAS获取许可
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

代码执行过程(以acquire()为例)

  1. 创建信号量对象
  2. 获取许可
  3. 释放许可
public class MyTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //1、信号量,只允许5个线程同时访问
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 100; i++) {
            executorService.submit(() -> {
                try {
                    //2、获取许可
                    semaphore.acquire();
                    Thread.sleep(1000);
                    //3、释放
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }
}
//1、构造方法
public Semaphore(int permits) {
        sync = new NonfairSync(permits);
}

//2、获取许可
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 获取许可,剩余许可 >= 0,则获取许可成功 <0 获取许可失败,进入排队
    if (tryAcquireShared(arg) < 0)
        //调用AQS的doAcquireSharedInterruptibly方法入队
        doAcquireSharedInterruptibly(arg);
}

//获取许可失败,当前线程进入同步队列,排队阻塞
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);
    }
}

//3、释放许可
public void release() {
        sync.releaseShared(1); //调用AQS的releaseShared方法
}

public final boolean releaseShared(int arg) {
        //调用Semaphore重写的tryReleaseShared方法CAS修改state
        if (tryReleaseShared(arg)) {
            //释放成功去队列唤醒线程
            doReleaseShared();
            return true;
        }
        return false;
}
//CAS修改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;
            }
}

以上demo使用的是acquire()方法,如果获取许可失败会阻塞线程并入队列,如果希望失败直接返回,可以使用tryAcquire()

//CAS修改state,失败直接return false
public boolean tryAcquire() {
        return sync.nonfairTryAcquireShared(1) >= 0;
}

 tryAcquire()示例:

public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量,只允许5个线程同时访问
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 100; i++) {
            executorService.submit(() -> {
                try {
                    //获取许可
                    if (!semaphore.tryAcquire()) {
                        return;
                    }
                    System.out.println(Thread.currentThread());
                    Thread.sleep(10000);
                    //释放
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }

结果可能是只有5个线程执行成功

Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-2,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-4,5,main]
Thread[pool-1-thread-5,5,main]

以上demo是为了演示Semaphore的使用过程,没有考虑线程中断异常等情况,可能会导致许可多释放或少释放。

比如:release 操作如果放到 finally 代码块里面去执行,可能会在acquire()发生中断异常,并未获取到令牌,导致release多释放令牌。

release 操作如果放到acquire()之后执行,两者在同一个try catch块,可能会在acquire()后发生异常,导致少释放令牌。

一种可能可用的做法是:在acquire()上catch异常,catch后直接return。未cacth到异常证明成功获取到许可,在后面的代码逻辑上加try cacth finally,在finally中执行release。

如果大家有更好的方法欢迎留言。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值