java多线程之Semaphore源码解析

前言

本篇开始分析Semaphore(信号量)的源码,分析结束后,会用一个示例展示Semaphore的应用场景。

1、简介

Semaphore是一个计数信号量,维护了一个信号量许可集。每次调用acquire()都将消耗一个许可,每次调用release()都将归还一个许可。

2、结构图

Semaphore的内部维护了一个Sync内部类,Sync是继承AQS的抽象类,Sync包括两个子类:"公平信号量"FairSync 和 "非公平信号量"NonfairSync。
在这里插入图片描述

3、分析源码

3.1、信号量构造函数

public Semaphore(int permits) {
	//默认构造非公平信号量
	sync = new NonfairSync(permits);
}

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

3.2、公平信号量获取

Semaphore中的方法源码:

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

public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
}

sync中调用的acquireSharedInterruptibly方法在AQS中,源码如下:

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    // 如果线程是中断状态,则抛出异常。
    if (Thread.interrupted())
        throw new InterruptedException();
    // 尝试获取“共享锁”;获取成功则直接返回,获取失败,则通过doAcquireSharedInterruptibly()获取
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

tryAcquireShared方法调用的是FairSync中的方法,源码如下:

protected int tryAcquireShared(int acquires) {
    for (;;) {
        // 判断当前队列前面还有没有等待的线程
        // 若有的话,则返回-1。
        if (hasQueuedPredecessors())
            return -1;
        // 设置“可以获得的信号量的许可数”
        int available = getState();
        // 设置剩余的信号量许可数
        int remaining = available - acquires;
        // 如果“剩余的信号量许可数>=0”,则设置“可以获得的信号量许可数”为remaining。
        //如果“剩余的信号量许可数小于0”,则进入doAcquireSharedInterruptibly方法
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

下面看看AQS中doAcquireSharedInterruptibly()的源码实现,此方法在ReentrantReadWriteLock中的doAcquireShared也分析过,再看看:

private void doAcquireSharedInterruptibly(long arg)
    throws InterruptedException {
    // 创建”当前线程“的Node节点,且Node中记录的锁是”共享锁“类型;并将该节点添加到CLH队列末尾。
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 当前节点的前一个节点。
            final Node p = node.predecessor();
            // 如果前一个节点是头节点(说明是第一个排队的节点)
            if (p == head) {
            	// 再次尝试获取读锁
                long 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.3、公平信号量释放

Semaphore中的方法源码:

public void release() {
     sync.releaseShared(1);
}
public void release(int permits) {
     if (permits < 0) throw new IllegalArgumentException();
     sync.releaseShared(permits);
}

AQS中的releaseShared方法,源码如下:

public final boolean releaseShared(int arg) {
  	if (tryReleaseShared(arg)) {
         doReleaseShared();
         return true;
     }
     return false;
}

Sync重写了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");
         // 如果原子更新state的值成功,就说明释放许可成功,则返回true
         if (compareAndSetState(current, next))
             return true;
     }
}

如果tryReleaseShared()尝试释放共享锁失败,则会调用doReleaseShared()去释放共享锁。doReleaseShared()的源码如下:

//此方法只会唤醒一个节点
//和ReentrantReadWriteLock中代码一样,不再解释
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;
	}
}

3.4、非公平信号量获取和释放

公平信号量和非公平信号量的释放是一样的,下面是非公平信号量的获取源码:

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        //这里和公平信号量相比,少了一个判断
        //少了的判断是:判断当前队列前面还有没有等待的线程
        //这里就体现了公平和非公平的区别
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

4、应用场景

通过上面的分析,我们知道了Semaphore的内部维护了一个字段state,用来限制同一时间对共同资源的访问次数,是不是非常像限流,现在我们就模拟限流场景,假设有1000个请求同时进来,我们只处理100个请求,防止服务器压力过大,代码如下:

public class SemaphoreLimit {

    public static final Semaphore SEMAPHORE = new Semaphore(100);
    private static final AtomicInteger SUCCESS = new AtomicInteger();

    public static void main(String[] args) {
        for (int i=0; i<1000; i++){
            new Thread(() -> { method();}).start();
        }
        System.out.printf("success request number=%d \n",SUCCESS.get());
    }
    
    public static void method(){
        if(!SEMAPHORE.tryAcquire()){
            return;
        }
        //处理业务
        try {
            Thread.sleep(100);
            SUCCESS.incrementAndGet();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            SEMAPHORE.release();
        }
    }
}

结束语

本篇分析了Semaphore源码,了解了Semaphore限流的应用场景,下一篇将分析CountDownLatch。

如果本篇的内容对你有帮助,请点个赞再走,谢谢大家!

原创文章 55 获赞 76 访问量 17万+

猜你喜欢

转载自blog.csdn.net/cool_summer_moon/article/details/106070799