Semaphore小述

1.概述

Semphore(信号量)是一种线程同步工具,它主要用于多个线程对同一个资源进行并行操作,合理并发占用有限的公共资源。Semaphore可以理解为管理一组数量固定许可证的机构,当有大量线程来请求资源时,Semaphore会给一部分线程许可证,拿到许可证的线程才能访问资源,访问结束后归还许可证给Semaphore,Semaphore继续将证书发给排队的一个个线程;使用Semaphore可以控制并发资源的线程个数。本文将通过案例对Semaphore进行演示,并通过源码进行分析,从而全面展示Semaphore的原理。

2.案例分析

首先分析一个场景:假如一个停车场有5个停车位,此时有10辆车都需要进行停车服务。正常的方案是应该给这10辆车编号,先让1-5号停车,剩下的5辆排队;然后等停车场每离开一辆车,再把这个车位分配给剩余5辆中的某辆。
根据案例编写的Demo如下:

public class Parking {

    static Semaphore semaphore = new Semaphore(5);

    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            final int temp = i;
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    log.info("车辆:" + temp + ",停车成功!");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    log.error("线程异常:{}", e);
                } finally {
                    log.info("线程:" + Thread.currentThread().getName() + "离开,释放车位:" + temp);
                    semaphore.release();
                }
            }, "Thread" + i).start();
        }
    }
}

运行结果如下图所示:
在这里插入图片描述
在上述代码中,我们首先初始化Semaphore的容量为5,即停车场的可停车辆为5,每次停车时,都会调用semaphore.acquire()方法来占用一个车位,车辆离开时,调用semaphore.release()来进行车位的释放。

3.源码分析

3.1 Sync

semaphore的类图如下图所示,它包含三个内部类:Sync、FailSync、NonfairSync。Sync内部类是继承于AQS(AbstractQueuedSynchronizer),并做了具体的实现。
在这里插入图片描述
Sync类的代码如下图所示:

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;
    
//构造方法,指定需要锁的数量
    Sync(int permits) {
        setState(permits);
    }
//调用AQS中getState()方法获取可用锁数量
    final int getPermits() {
        return getState();
    }
//非公平方式加锁,直接开启线程加锁
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
        	//获取当前剩余锁数量
            int available = getState();
            //计算获取锁后,剩余锁数量(可用锁-请求锁数量)
            int remaining = available - acquires;
            //CAS加锁操作,失败则自旋
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                //大于0表示加锁成功,小于0表示失败
                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");
            //调用AQS中compareAndSetState方法进行加锁,成功返回true,失败则自旋
            if (compareAndSetState(current, next))
                return true;
        }
    }

//无返回值,加锁
    final void reducePermits(int reductions) {
        for (;;) {
            //获取当前剩余锁数量
            int current = getState();
            //计算获取锁后,剩余锁数量(可用锁-请求锁数量)
            int next = current - reductions;
             //判断是否有异常未释放锁的情况
            if (next > current) // underflow
            //存在线程未正常释放锁的情况
                throw new Error("Permit count underflow");
            if (compareAndSetState(current, next))
            //调用AQS中compareAndSetState方法进行加锁,失败则自旋
                return;
        }
    }

//一次性获取剩余锁数量并全部加锁
    final int drainPermits() {
        for (;;) {
        //获取当前剩余锁数量
            int current = getState();
            //获取当前线程中所有锁并加锁
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}

3.2 FailSync(公平性)

static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;
		//构造方法,指定需要锁的数量
        FairSync(int permits) {
            super(permits);
        }
		
        protected int tryAcquireShared(int acquires) {
        //死循环
            for (;;) {
            //判断AQS的CLH队列中是否有等待队列,若有,则直接返回-1,表示加锁失败
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

3.3 NonfairSync(非公平性)

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

这里解释一下公平性和非公平性,其实和ReentrantLock一样,Semaphore也存在公平和非公平性两种情况。不公平意味着它不会去保证线程获取许可的顺序,Semaphore 会在线程等待之前为调用 acquire 的线程分配一个许可,拥有这个许可的线程会自动将自己置于线程等待队列的头部。当这个参数为 true 时,Semaphore 确保任何调用 acquire 的方法,都会按照先入先出的顺序来获取许可。由下面构造方式可知,Semaphore默认是非公平锁,可以根据传入的fair值来选择公平锁。

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

从上述NonfairSync和FailSync的源码可以看出,他们最大的区别在于tryAcquireShared。NonfairSync不管当前线程前是否有线程在排队,而是直接判断信号许可量和CAS方法是否可行。FairSync会先去判断队列中是否有线程,如果有的话则直接获取失败。

3.2 核心方法解析

方法名称源码解释
acquire()public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1);}尝试获取一个许可,如果获取失败,则自旋等待,直到其它线程释放许可或该线程被中断
acquire(int permits)public void acquire(int permits) throws InterruptedException { if (permits < 0) throw new IllegalArgumentException();sync.acquireSharedInterruptibly(permits);}尝试获取指定数量许可,如果获取失败,则自旋等待,直到其它线程释放许可或该线程被中断
acquireUninterruptibly()public void acquireUninterruptibly() { sync.acquireShared(1);}尝试获取一个许可,方法作用是在等待许可的情况下不允许被中断,如果成功获得锁,则取得许可成功
acquireUninterruptibly(int permits)public void acquireUninterruptibly(int permits) { if (permits < 0) throw new IllegalArgumentException();sync.acquireShared(permits); }尝试获取指定数量许可,方法作用是在等待许可的情况下不允许被中断,如果成功获得锁,则取得指定的permits许可个数
tryAcquire()public boolean tryAcquire() {return sync.nonfairTryAcquireShared(1) >= 0;}尝试获得一个许可,如果获得许可成功返回true,如果失败则返回fasle,它不会等待,立即返回
tryAcquire(int permits)public boolean tryAcquire(int permits) {if (permits < 0) throw new IllegalArgumentException();return sync.nonfairTryAcquireShared(permits) >= 0;}尝试获得指定数量许可,如果获得许可成功返回true,如果失败则返回fasle,它不会等待,立即返回
tryAcquire(long timeout, TimeUnit unit)public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));}尝试在指定时间内获得一个许可,如果在指定时间内没有获得许可则则返回false,反之返回true
tryAcquire(int permits, long timeout, TimeUnit unit)public boolean tryAcquire(int permits, long timeout, TimeUnit unit)throws InterruptedException {if (permits < 0) throw new IllegalArgumentException();return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));}尝试在指定时间内获得指定数量许可,如果在指定时间内没有获得许可则则返回false,反之返回true
release()public void release() { sync.releaseShared(1);}用于在线程访问资源结束后,释放一个许可,以使其他等待许可的线程可以进行资源访问
release(int permits)public void release(int permits) {if (permits < 0) throw new IllegalArgumentException();sync.releaseShared(permits);}用于在线程访问资源结束后,释放指定数量许可,以使其他等待许可的线程可以进行资源访问
availablePermits()public int availablePermits() {return sync.getPermits();}查看当前可用许可数
drainPermits()public int drainPermits() {return sync.drainPermits();}获取当前所有可用的许可
reducePermits(int reduction)protected void reducePermits(int reduction) {if (reduction < 0) throw new IllegalArgumentException();sync.reducePermits(reduction);}尝试获取一个许可,如果获取失败,则自旋等待,直到其它线程释放许可或该线程被中断
isFair()public boolean isFair() {return sync instanceof FairSync;}判断是否是公平锁
hasQueuedThreads()public final boolean hasQueuedThreads() {return sync.hasQueuedThreads();}查看当前队列中是否有线程等待
getQueueLength()public final int getQueueLength() {return sync.getQueueLength();}获取当前等待线程队列的长度
getQueuedThreads()protected Collection< Thread> getQueuedThreads() {return sync.getQueuedThreads(); }获取当前队列中线程列表
toString()public String toString() {return super.toString() + "[Permits = " + sync.getPermits() + “]”;}toString方法

4.小结

1.Semaphore与CountDownLatch、CyclicBarrier类似,都可以控制线程访问数量;
2.Semphore底层主要还是基于AQS的,方法入口均是Sync类,默认使用非公平锁;
3.Semphore的核心在于,通过CAS在加锁时判断是否有足够的锁数量,在释放锁更新锁的数量。

5.参考文献

1.https://juejin.cn/post/6844904083216662536
2.https://juejin.cn/post/6950219938929836063
3.https://juejin.cn/post/6844903537508368398
4.https://juejin.cn/post/6951588688262332430
5.https://www.bilibili.com/video/BV1mQ4y1Z795

6.附录

附录展示了Semaphore的内部方法图:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值