Java并发编程系列:Semaphore

Semaphore简介

无论是内部锁synchroized还是RentrantLock,同一时刻只允许一个线程访问资源,而信号量(Semaphore)却可以指定多个线程同时访问一个资源。Semaphore相当于共享锁版的RentrantLock。
主要流程为:

  1. 创建一定数量的许可
  2. 不断的消耗许可,当许可被消耗完毕,后面的线程进行队列排队,设置头节点
  3. 当有许可被释放的时候,唤醒节点进行许可申请。重置 state 许可数量

# 使用方式

Semaphore semaphore = new Semaphore(5);
semaphore.acquire();
semaphore.release();
# 方法解释

java public Semaphore(int permits):创建具有给定的许可数和非公平的公平设置的 Semaphore,公平指的是排队阻塞的线程先进先出的规则。

javapublic Semaphore(int permits, boolean fair)创建具有给定的许可数和给定的公平设置的 Semaphore。
第一个参数是信号量的准入数,即同事能申请多少个许可,当每个线程只申请一个许可时,就那相当于指定了有多少个线程可以同事访问某个资源。

acquire()尝试获得一个准入的许可,若无法获得,则线程会等待,直到有线程释放许可或者当前线程被中断。
release()释放一个许可,将其返回给信号量。

# 一个Demo
public class SemapDemo implements Runnable {
	final static Semaphore semp = new Semaphore(5);
	public void run(){
		try {
			semp.acquire();
			Thread.sleep(1000);
			System.out.println(Thread.currentThread().getName()+" done");;
			semp.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public static  void main(String args[]) {
		ExecutorService exec = Executors.newFixedThreadPool(20);
		final SemapDemo demo = new SemapDemo();
		for(int i=0;i<20;i++){
			exec.submit(demo);
		}
	}
} 
原理分析

Semaphore类的结构图如下,其中红色的线表示内部类,蓝色的线表示继承。
Semaphore
根据构造方法的参数,来构建内部公平或者非公平的Sync实例,赋值给成员变量Sync,后面的acquire和release方法都是委托给sync来操作。

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

根据构造函数的调用链,可以看出permits最终赋值于AQS的state属性。这里和CountDownLatch颇为相似。state在CountDownLatch终表示计数器的值,即几个线程可以去调用countDown方法,而在Semaphore中state表示许可的数量。

# acquire

acquire的调用流程为:Semaphore.acquire -> AQS.acquireSharedInterruptibly -> NonfairSync/FairSync.tryAcquireShared -> AQS.doAcquireSharedInterruptibly
tryAcquireShared根据不同的公平锁和非公平锁的实现,作用是尝试获取锁,如果没有获取到锁,才回去执行doAcquireSharedInterruptibly,doAcquireSharedInterruptibly的作用是入队、尝试、挂起线程。

我们直接进入AQS的acquireSharedInterruptibly方法,在分析CountDownLatch中也讲过该方法。

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

如果获取许可的结果>=0,表示获取成功。否则,进入doAcquireSharedInterruptibly方法。

// 非公平锁的释放锁的方法
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
    	// 获取许可的数量
        int available = getState();
        // 计算剩余数量
        int remaining = available - acquires;
        // 如果小于 0,直接返回remaining,表示获取锁失败,如果大于0,CAS设置。
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

接着doAcquireSharedInterruptibly方法也是在CountDownLatch中分析过,此处就不贴代码了,其大体逻辑为:

  1. 创建一个共享型的节点添加至同步队列的尾部
  2. 如果这个节点的上一个节点是 head ,就是尝试获取锁,获取锁的方法就是子类重写的方法。如果获取成功了,就将刚刚的那个节点设置成 head。
  3. 如果前一个节点不是head,或者获取锁失败,则去挂起线程
#release

release过程和CountDownLatch的countDown逻辑基本一样,通过AQS的releaseShared方法内部调用子类实现的tryReleaseShared法方法,表示释放许可,接着唤醒阻塞的线程。这里有个点需要
注意下即使在某个线程中没有调用acquire方法,也可以直接使用release,相当于增加许可的数量。

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
    	// 唤醒 AQS 等待对列中的节点,从 head 开始	
        doReleaseShared();
        return true;
    }
    return false;
}
// Sync extends AbstractQueuedSynchronizer 
protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < curre
        if (compareAndSetState(current, next))
            return true;
    }
}

逻辑很简单,唤醒线程方法doReleaseShared在CountDownLatch中分析过,此处不再赘述。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值