java8 Semaphore原理详解

阅读本文前,请先查看《java8 多线程并发之AQS详解》和《java8 ReentrantLock加解锁原理详解》,本文是在前面两篇文章基础上的延续。
Semaphore类表示信号量,初始时我们设置一个信号量初始值,该初始值可以为任意值(负数也可以,一般为正值),每个线程可以申请一定量的信号量,如果信号量不够,则阻塞线程,每个线程执行完,会将信号量释放,以供后续线程申请使用。Semaphore允许多个线程同时持有一定量的信号量,因此Semaphore相当于共享锁,信号量初始值就相当于锁的个数,只要线程持有的锁个数不超过总的锁个数,就允许多个线程同时访问资源。

一、如何使用Semaphore

下面是一个使用Semaphore控制多线程访问资源的例子。

public class Main {
    public static void main(String argv[]){
        //初始信号量为10个
        Semaphore s=new Semaphore(4);
        AtomicInteger cnt=new AtomicInteger(0);
        Runnable run=()->{
            s.acquireUninterruptibly(2);
            int no=cnt.getAndAdd(1);
            System.out.println("线程"+no+"-开始运行时间为:"+(new Date()));
            try {
                Thread.sleep(3000);//模拟访问资源
            }catch (Exception e){}
            s.release(2);
            System.out.println("线程"+no+"-结束运行时间为:"+(new Date()));
        };
        for(int i=0;i<6;i++){
            Thread thread=new Thread(run);
            thread.start();
        }
    }
}

运行结果为:

线程0-开始运行时间为:Sat Jan 30 16:38:23 CST 2021
线程1-开始运行时间为:Sat Jan 30 16:38:23 CST 2021
线程2-开始运行时间为:Sat Jan 30 16:38:26 CST 2021
线程1-结束运行时间为:Sat Jan 30 16:38:26 CST 2021
线程3-开始运行时间为:Sat Jan 30 16:38:26 CST 2021
线程0-结束运行时间为:Sat Jan 30 16:38:26 CST 2021
线程4-开始运行时间为:Sat Jan 30 16:38:29 CST 2021
线程2-结束运行时间为:Sat Jan 30 16:38:29 CST 2021
线程5-开始运行时间为:Sat Jan 30 16:38:29 CST 2021
线程3-结束运行时间为:Sat Jan 30 16:38:29 CST 2021
线程4-结束运行时间为:Sat Jan 30 16:38:32 CST 2021
线程5-结束运行时间为:Sat Jan 30 16:38:32 CST 2021

初始信号量为4个,每个线程申请两个,那么最多可以同时有两个线程运行。所以随着线程不断的申请信号量,当信号量个数小于等于0的时候,如果后续线程再来申请,那么会阻塞线程。
Semaphore的初始信号量并没有要求必须大于0,小于等于0也可以,但是此时必须首先有线程释放信号量,否则该信号量会阻塞所有的申请线程。
Semaphore也有公平信号量和非公平信号,其含义与ReentrantLock的公平锁和非公平锁一样,这里不再详细介绍。

二、源码解析

1、构造方法

Semaphore提供了两个构造方法:

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

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

第二个构造方法的fair用于设置公平信号量和非公平信号量。
从构造方法可以看到,NonfairSync表示非公平信号量,FairSync表示公平信号量。Semaphore中的方法基本上都是委托给这两个类处理的,比如:

    public void acquireUninterruptibly() {
        sync.acquireShared(1);
    }

因此Semaphore的核心逻辑都在NonfairSync和FairSync中,下面重点介绍这两个类的实现逻辑。

2、Sync

Sync是NonfairSync和FairSync的共同基类,同时Sync继承自AQS。

    abstract static class Sync extends AbstractQueuedSynchronizer {
        Sync(int permits) {
        	//信号量的初始值直接设置给了AQS中的属性state
        	//因此属性state就表示了总的信号量
            setState(permits);
        }
		//返回当前剩余信号量
        final int getPermits() {
            return getState();
        }
		//申请信号量,使用当前剩余信号量与申请的信号量比较,如果前者大,则修改剩余信号量,并将该值返回,
		//如果后者大,则不做改变,将还需要增加的信号量个数返回。
		//该方法在非公平信号量中使用
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
		//释放信号量
		//信号量相当于AQS的共享模式,因此需要实现tryReleaseShared()
        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;
            }
        }
		//减少信号量个数,
		//该方法由Semaphore.reducePermits(int reductions)方法调用,入参reductions必须大于0,
		//该方法可以同于跟踪不可用资源,当资源减少,信号量个数也需要同步减少,那么便可以调用该方法减少信号量
        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))
                    return;
            }
        }
		//返回剩余信号量,同时将剩余信号量修改为0
        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }

3、FairSync

FairSync表示公平信号量。

	//代码有删减
    static final class FairSync extends Sync {
        FairSync(int permits) {
            super(permits);
        }
		//信号量相当于AQS的共享模式,因此FairSync需要实现tryAcquireShared()
        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;
            }
        }
    }

FairSync实现了AQS共享模式下的tryAcquireShared()方法,每次调用Semaphore中申请信号量的方法(acquire()/acquireUninterruptibly()等)都会调用tryAcquireShared()方法,该方法首先检查队列中是否有等待信号量的线程,如果有,AQS会将当前线程放到队列中,并阻塞线程,等待后面重新调度;如果没有则直接尝试修改属性state,修改成功则表示信号量申请成功。
在公平信号量中,FairSync严格按照先进先出的原则申请信号量。

4、NonfairSync

NonfairSync表示非公平信号量。

    //代码有删减
    static final class NonfairSync extends Sync {
        NonfairSync(int permits) {
            super(permits);
        }
        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }

NonfairSync的tryAcquireShared()方法直接调用Sync的nonfairTryAcquireShared()方法,nonfairTryAcquireShared()方法在前面已经介绍过了,与FairSync相比,非公平信号量不关心队列中是否有等待的线程,而是直接尝试申请,只有申请失败了才会进入队列中,之后在队列里面按照先进先出的原则等待再次申请信号量。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值