Semaphore 使用及原理

Semaphore是Java中用于线程同步的一种工具,它能控制同时访问特定资源的线程数量。Semaphore常用于限流场景,如数据库连接池和停车场模拟。本文详细介绍了Semaphore的构造、方法、工作原理,并通过代码示例展示了如何使用Semaphore实现停车场的剩余车位管理。当车位不足时,车辆将被阻塞等待。此外,还解析了Semaphore的获取和释放令牌的内部机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、Semaphore 是什么

Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

可以把它简单的理解成我们停车场入口立着的那个显示屏,每有一辆车进入停车场显示屏就会显示剩余车位减1,每有一辆车从停车场出去,显示屏上显示的剩余车辆就会加1,当显示屏上的剩余车位为0时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止。

2、使用场景

朱勇用于那些资源有明确访问数量限制的场景,常用于限流 。

比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。

比如:停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。

3、Semaphore常用方法说明

信号量的构造函数
非公平:

public Semaphore(int permits);//permits就是允许同时运行的线程数目

公平(获得锁的顺序与线程启动顺序有关):

public Semaphore(int permits,boolean fair);//permits就是允许同时运行的线程数目

acquire()  
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
​
acquire(int permits)  
获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
    
acquireUninterruptibly() 
获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
    
tryAcquire()
尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
​
tryAcquire(long timeout, TimeUnit unit)
尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
​
release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
​
hasQueuedThreads()
等待队列里是否还存在等待线程。
​
getQueueLength()
获取等待队列里阻塞的线程数。
​
drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。
​
availablePermits()
返回可用的令牌数量。

4、用semaphore 实现停车场提示牌功能。

每个停车场入口都有一个提示牌,上面显示着停车场的剩余车位还有多少,当剩余车位为0时,不允许车辆进入停车场,直到停车场里面有车离开停车场,这时提示牌上会显示新的剩余车位数。

业务场景 :

1、停车场容纳总停车量10。

2、当一辆车进入停车场后,显示牌的剩余车位数响应的减1.

3、每有一辆车驶出停车场后,显示牌的剩余车位数响应的加1。

4、停车场剩余车位不足时,车辆只能在外面等待。

代码:

public class TestCar {
​
    //停车场同时容纳的车辆10
    private  static  Semaphore semaphore=new Semaphore(10);
​
    public static void main(String[] args) {
​
        //模拟100辆车进入停车场
        for(int i=0;i<100;i++){
​
            Thread thread=new Thread(new Runnable() {
                public void run() {
                    try {
                        System.out.println("===="+Thread.currentThread().getName()+"来到停车场");
                        if(semaphore.availablePermits()==0){
                            System.out.println("车位不足,请耐心等待");
                        }
                        semaphore.acquire();//获取令牌尝试进入停车场
                        System.out.println(Thread.currentThread().getName()+"成功进入停车场");
                        Thread.sleep(new Random().nextInt(10000));//模拟车辆在停车场停留的时间
                        System.out.println(Thread.currentThread().getName()+"驶出停车场");
                        semaphore.release();//释放令牌,腾出停车场车位
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },i+"号车");
​
            thread.start();
​
        }
​
    }
}
​

5、Semaphore实现原理

(1)、Semaphore初始化。

Semaphore semaphore=new Semaphore(2);

1、当调用new Semaphore(2) 方法时,默认会创建一个非公平的锁的同步阻塞队列。

2、把初始令牌数量赋值给同步队列的state状态,state的值就代表当前所剩余的令牌数量。

初始化完成后同步队列信息如下图:

(2)获取令牌

semaphore.acquire();

1、当前线程会尝试去同步队列获取一个令牌,获取令牌的过程也就是使用原子的操作去修改同步队列的state ,获取一个令牌则修改为state=state-1。

2、 当计算出来的state<0,则代表令牌数量不足,此时会创建一个Node节点加入阻塞队列,挂起当前线程。

3、当计算出来的state>=0,则代表获取令牌成功。

源码:

/**
     *  获取1个令牌
     */
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

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

/**
     * 1、创建节点,加入阻塞队列,
     * 2、重双向链表的head,tail节点关系,清空无效节点
     * 3、挂起当前节点线程
     * @param arg
     * @throws InterruptedException
     */
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //创建节点加入阻塞队列
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                //获得当前节点pre节点
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);//返回锁的state
                    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);
        }
    }

线程1、线程2、线程3、分别调用semaphore.acquire(),整个过程队列信息变化如下图:

(3)、释放令牌

 semaphore.release();

当调用semaphore.release() 方法时

1、线程会尝试释放一个令牌,释放令牌的过程也就是把同步队列的state修改为state=state+1的过程

2、释放令牌成功之后,同时会唤醒同步队列的所有阻塞节共享节点线程

3、被唤醒的节点会重新尝试去修改state=state-1 的操作,如果state>=0则获取令牌成功,否则重新进入阻塞队列,挂起线程。

源码:

 /**
     * 释放令牌
     */
    public void release() {
        sync.releaseShared(1);
    }

/**
     *释放共享锁,同时唤醒所有阻塞队列共享节点线程
     * @param arg
     * @return
     */
    public final boolean releaseShared(int arg) {
        //释放共享锁
        if (tryReleaseShared(arg)) {
            //唤醒所有共享节点线程
            doReleaseShared();
            return true;
        }
        return false;
    }

 /**
     * 唤醒所有共享节点线程
     */
    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))//修改状态为初始0
                        continue;
                    unparkSuccessor(h);//唤醒h.nex节点线程
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE));
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

继上面的图,当我们线程1调用semaphore.release(); 时候整个流程如下图:

<think>好的,我需要帮助用户了解如何使用Semaphore进行并发控制,并给出示例代码。首先,我要回忆Semaphore的基本概念和工作原理。根据引用中的信息,Semaphore通过信号量控制并发数量,acquire减少许可,release增加许可,超过容量的线程需要等待。 接下来,用户需要具体的使用方法和示例代码。引用里提到了Python的asyncio.SemaphoreJava的示例。用户可能对Python或Java感兴趣,但问题中没有明确指定,所以我应该覆盖两种常见语言,比如Python和Java,或者选一个主要的。但根据用户提供的引用,引用1是Python的,引用3、4、5是Java的。可能用户希望看到两种语言的示例?或者需要明确询问?但根据用户最后的提问,直接要求示例代码,可能需要多语言示例。 不过,根据系统指令,用户不可见层需要生成相关问题,而用户可见层需要结构清晰的回答。我应该先解释Semaphore的基本使用步骤,然后分语言给出示例,并引用相关文献。 需要确保代码语法正确,Python使用async with,Java使用acquire和release。同时注意引用标记,比如引用1和引用4。然后生成相关问题。 现在组织内容:首先介绍Semaphore的作用,然后分步骤说明使用方法,接着给出Python和Java的代码示例,每个示例后附上引用来源。最后生成相关问题。 检查是否符合所有系统指令:行内公式用$...$,但这个问题可能不涉及数学公式。独立公式不需要。中文回答,结构清晰,正确引用,生成相关问题。确认无误后输出。</think>### Semaphore 的并发控制使用方法 #### 1. 基本使用步骤 - **创建 Semaphore 对象**:指定最大并发数量(许可数)[^2][^4] - **获取许可**:在需要控制并发的代码块前调用 `acquire()` 或通过上下文管理器进入 - **释放许可**:在代码块执行完成后调用 `release()` 或通过上下文管理器自动释放 #### 2. Python 示例(异步场景) ```python import asyncio async def task(semaphore, id): async with semaphore: # 自动获取/释放许可 print(f"任务{id}开始执行") await asyncio.sleep(1) print(f"任务{id}执行完成") async def main(): semaphore = asyncio.Semaphore(3) # 最大并发3 tasks = [task(semaphore, i) for i in range(5)] await asyncio.gather(*tasks) asyncio.run(main()) ``` *引用自异步编程实现[^1]* #### 3. Java 示例(线程同步) ```java import java.util.concurrent.Semaphore; public class Demo { private static final Semaphore semaphore = new Semaphore(3); // 最大并发3 public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(() -> { try { semaphore.acquire(); // 获取许可 System.out.println("任务开始:" + Thread.currentThread().getId()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); // 释放许可 System.out.println("任务完成:" + Thread.currentThread().getId()); } }).start(); } } } ``` *引用自线程并发控制实现[^4]* #### 4. 关键机制 - 当许可耗尽时,后续线程进入阻塞状态 - 公平性可通过构造函数参数配置(Java 支持公平锁)[^4] - 支持尝试获取许可(`tryAcquire`)和超时机制[^5] §§ 相关问题 §§ 1. Semaphore 与 Mutex 锁有什么区别? 2. 如何防止 Semaphore 的许可泄漏问题? 3. Semaphore数据库连接池中的应用? 4. 如何实现 Semaphore 的跨进程控制? 5. Semaphore 的公平模式对性能有什么影响? : Python异步信号量实现 [^2]: Semaphore基础工作机制 : Java并发控制示例 : 单许可信号量实现
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值