Semaphore(信号量)源码解读与使用

简介: Semaphore(信号量)源码解读与使用

1. 前言

在前面的文章我们讲解了CountDownLatch倒计时器的基本使用,本节我们继续来学习Java多线程编程中的一个工具类Semaphore信号量。

2. 什么是Semaphore?

Semaphore 是一个在多线程环境中用于控制对共享资源的访问的同步器(synchronizer),它是 Java 5 中引入的 java.util.concurrent(JUC)包的一部分。Semaphore 维护了一个许可集,线程在执行前必须从 Semaphore 获取一个许可。如果没有许可可用,线程将阻塞等待,直到其他线程释放许可。可以用来限制数据库连接数、限制服务器可处理请求数等。

3. Semaphore源码解读

我们跟进信号量的源码中浏览一圈,发现其实它内部主要的方法就2个:

// 初始共享资源数量
final Semaphore semaphore = new Semaphore(5);
// 获取1个许可
semaphore.acquire();
// 释放1个许可
semaphore.release();


3.1 acquire():获取许可

 3.1 acquire():获取许可

跟进这个方法后,我们会发现其内部调用了AQS的一个final 方法acquireSharedInterruptibly(),

/**
 * 共享模式下获取许可证,获取成功则返回,失败则加入阻塞队列,挂起线程
 */
public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
      throw new InterruptedException();
        // 尝试获取许可证,arg为获取许可证个数,当可用许可证数减当前获取的许可证数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程。
    if (tryAcquireShared(arg) < 0)
      doAcquireSharedInterruptibly(arg);
}


我们可以发现这个方法中又调用了tryAcquireShared(arg)方法,作为AQS中的钩子方法,这个方法的实现在Semaphore的两个静态内部类 FairSync(公平模式) 和 NonfairSync(非公平模式) 中。虽然这个方法在AQS中,但它作为钩子方法,最终的实现则回到了Semaphore的内部类中。


我们接下来继续查看在 FairSync(公平模式)中的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;
                // 获取可用的许可数
                int available = getState();
                // 计算扣减后的许可数
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }


这里就是获取剩余的许可数值,然后计算扣减后的许可数值,如果扣减后的值小于0,就会直接返回扣减数(||短路或,前者满足就不用判断后者了)。否认就会执行CAS操作compareAndSetState(available, remaining)扣减许可数的值。


这里我们就可知,Semaphore信号量的许可数是存放在AQS中的state变量中的(AQS源码我后续应该也会发文讲解),然后通过CAS来修改state的值,保证了操作的原子性。


3.2 release():释放许可

同样跟入这个方法,里面用了AQS的releaseShared(),而在这个方法内也毫无疑问的用了tryReleaseShared(int arg)这个钩子方法,原理同上,不再冗释,需要注意的是释放共享锁(也就是增加许可数state的值)的同时也会唤醒同步队列中的一个线程。

// 释放一个许可证
public void release() {
    sync.releaseShared(1);
}
 
// 释放共享锁,同时会唤醒同步队列中的一个线程。
public final boolean releaseShared(int arg) {
    // 尝试释放共享锁(增加许可数)
    if (tryReleaseShared(arg)) {
      //唤醒同步队列中的一个线程
      doReleaseShared();
      return true;
    }
    return false;
}
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;
            }
      }
 

4. Semaphore的使用

OK,讲到这里,把信号量中主要的方法解释完了,我们来写一个小demo感受一下它的使用:

public class Test {
    private final Semaphore semaphore;
 
    /*构造一个令牌*/
    public Test(int acq){
        this.semaphore= new Semaphore(acq);
    }
    public void useSemaphore(){
        try {
            semaphore.acquire();
            // 使用资源
            System.out.println("资源使用 " + Thread.currentThread().getName());
            Thread.sleep(1000); // 模拟资源使用时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
            System.out.println("资源释放 " + Thread.currentThread().getName());
        }
    }
 
    public static void main(String[] args) {
        Test test = new Test(3);
        for (int i = 0; i < 5; i++) {
            new Thread(test::useSemaphore).start();
        }
    }
}

输出:

资源使用 Thread-0
资源使用 Thread-1
资源使用 Thread-3
资源释放 Thread-0
资源使用 Thread-2
资源使用 Thread-4
资源释放 Thread-1
资源释放 Thread-3
资源释放 Thread-4
资源释放 Thread-2


5. 总结


Semaphore是基于AQS和CAS操作实现的共享锁,利用AQS中被volatile修饰的变量state来代表许可证数(permit),当用户调用acquire()方法来获取许可证数时,他会计算出本次取出操作后的许可证数,如果小于0,那么就会挂起等待,如果大于0,也就是许可证数还够用,那么就会使用CAS操作修改许可证数。当调用release()方法释放许可证数时,会唤醒等待的线程去获取共享锁


如果本文对你有帮助,点个小赞吧,后续有时间就多出出这方面的源码解读

相关文章
|
2月前
|
Java
JAVA并发编程系列(7)Semaphore信号量剖析
腾讯T2面试,要求在3分钟内用不超过20行代码模拟地铁安检进站过程。题目设定10个安检口,100人排队,每人安检需5秒。实际中,这种题目主要考察并发编程能力,特别是多个线程如何共享有限资源。今天我们使用信号量(Semaphore)实现,限制同时进站的人数,并通过信号量控制排队和进站流程。并详细剖析信号量核心原理和源码。
|
3月前
Semaphore原理
文章概述了Semaphore的工作原理及其使用场景:Semaphore是一种有效的限流工具,可以用来控制同一时刻访问共享资源的线程数量。它适用于需要对资源访问进行限流的场景,以避免资源过载或实现公平的资源分配。
Semaphore原理
|
6月前
多线程并发之Semaphore(信号量)使用详解
多线程并发之Semaphore(信号量)使用详解
2208 0
Semaphore使用及原理解读
Semaphore使用及原理解读
Semaphore使用及原理解读
|
Java
Java多线程:Semaphore
Java多线程:Semaphore
116 0
|
Java 测试技术
Semaphore原理剖析
Semaphore原理剖析
145 0
|
Java 数据库连接 API
【JUC】信号量Semaphore详解
【JUC】信号量Semaphore详解
174 0
【JUC】信号量Semaphore详解
并发编程之Semaphore信号量
`Semaphore` 翻译过来就是信号量, 其根本原理就是基于 `CAS` 共享锁的一种实现。举一个例子。 假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。
169 0
Semaphore 信号量源码分析
Semaphore 信号量源码分析
Semaphore 信号量源码分析
|
Java 数据库连接 数据库
Java并发编程 - AQS 之 Semaphore(二)
Java并发编程 - AQS 之 Semaphore(二)
113 0
Java并发编程 - AQS 之 Semaphore(二)