Semaphore使用以及原理

本文介绍了Semaphore信号量的基本原理,如何通过AQS实现并发访问控制,举例说明了如何用Semaphore控制线程并发访问公共资源,并深入剖析了acquire()和release()方法的源码。适合理解并发控制在数据库连接等场景的应用。

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

1. Semaphore的使用

1) Semaphore,俗称信号量 基于AbstractQueuedSynchronizer实现!AQS

2) Semaphore管理着一组许可permit,许可的初始数量通过构造函数设定。

3) 默认使用非公平的方式 sync = new NonfairSync(permits);
   使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数

4) 当线程要访问共享资源时,需要先通过acquire()方法获取许可。获取到之后许可就被当前线程占用了,在归还许可之前其他线程不能获取这个许可。
调用acquire()方法时,如果没有许可可用了,就将线程阻塞,等待有许可被归还了再执行。
当执行完业务功能后,需要通过release()方法将许可证归还,以便其他线程能够获得许可证继续执行。
如果初始化了一个许可为1的Semaphore,那么就相当于一个不可重入的互斥锁(Mutex)。

举个生活中的小栗子:

我们假设停车场仅有3个停车位,停车位就是有限的共享资源,许可数为3。一开始停车场没有车辆所有车位全部空着,然后先后到来三辆车,停车场车位够,安排进去停车。之后来的车必须在外面候着,直到停车场有空车位。当停车场有车开出去,里面有空位了,则安排一辆车进去(至于是哪辆要看选择的机制是公平还是非公平)。

从程序角度看,停车场就相当于有限的公共资源,许可数为3,车辆就相当于线程。当来一辆车时,许可数就会减1,当停车场没有车位了(许可数为0),其他来的车辆需要在外面等候着。如果有一辆车开出停车场,许可数+1,然后放进来一辆

2.代码举例实现

package com.concurrent;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;


public class SemaphoreDemo {
    public static void main(String[] args) {


        //Semaphore管理着一组许可permit,许可的初始数量通过构造函数设定。

        /**
         * Semaphore的主要方法摘要:
         *
         *   void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
         *
         *   void release():释放一个许可,将其返回给信号量。
         *
         *   int availablePermits():返回此信号量中当前可用的许可数。
         *
         *   boolean hasQueuedThreads():查询是否有线程正在等待获取。
         */
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <=6 ; i++) {
                  new Thread(() -> {

                      try {
                          semaphore.acquire();
                          System.out.println(Thread.currentThread().getName()+"\t 抢到了车位");
                           try {
                                 TimeUnit.SECONDS.sleep(3);
                                } catch (Exception e) {
                                 e.printStackTrace();
                             }
                          System.out.println(Thread.currentThread().getName()+"\t 停车3s后离开");
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }finally {
                          semaphore.release();
                      }

                  },String.valueOf(i)).start();
        }

    }
}

代码结果输出:

1     抢到了车位
3     抢到了车位
2     抢到了车位
1     停车3s后离开
4     抢到了车位
3     停车3s后离开
2     停车3s后离开
5     抢到了车位
6     抢到了车位
5     停车3s后离开
6     停车3s后离开
4     停车3s后离开

Semaphore可以用于做流量控制,特别是公共资源有限的应用场景,比如数据库连接。假如有多个线程读取数据后,需要将数据保存在数据库中,而可用的最大数据库连接只有10个,这时候就需要使用Semaphore来控制能够并发访问到数据库连接资源的线程个数最多只有10个。在限制资源使用的应用场景下,Semaphore是特别合适的。

3.源码分析 acquire()

acquire()方法就是获取许可,获取到许可就可以继续执行访问共享资源,获取不到就阻塞等待其他线程归还许可。

AQS.state用来记录可用的许可数量,每获取一个许可state减1。

/*** 获取许可的方法其实就是获取锁的方法 */

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

// 真正获取锁的方法,由Semaphore.NonfairSync实现 doAcquireSharedInterruptibly(arg); // 获取锁失败,当前线程阻塞并进入AQS同步队列}/** * Semaphore.NonfairSync实现的获取锁的方法 *

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int 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);
    }
}

** * 每获取一个许可,将state-1,state表示剩余的许可数 * 如果许可已经用完,返回remaining<0,表示获取不到锁/许可,线程阻塞 * 如果还有许可,返回remaining>=0,表示获取到锁/许可,线程继续执行 

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

4.源码分析 release()

release()方法归还许可,其实就是将AQS.state加1。归还成功,唤醒AQS队列中等锁的线程,从被阻塞的位置开始执行。

public final boolean releaseShared(int arg)
{
    if (tryReleaseShared(arg)) {
        // 释放锁,由Semaphore.Sync实现
        doReleaseShared();
        // 释放锁成功,唤醒AQS队列中等锁的线程
        return true;
    }
       return false;
      }
      /** * 每归还一个许可将state加1 */
      protected final boolean tryReleaseShared(int releases) {
          for (;;) {
              int current = getState();
              int next = current + releases;// 每归还一个许可将state加1
              // if (next < current) // overflow throw new Error("Maximum permit count exceeded");
              if (compareAndSetState(current, next))
                  return true;
          }
      }
}

5.小总结

信号量Semaphore用于控制资源能够被并发访问的线程数量,以保证多个线程能够合理的使用特定资源,比如数据库连接等。

Semaphore在构造时设置一个许可数量,这个许可数量用AQS.state来记录。

acquire()方法就是获取许可,只有获取到许可才可以继续执行访问共享资源,获取到许可之后AQS.state减1,以记录当前可用的许可数量;如果获取不到许可,线程就阻塞等待其他线程归还许可。

release()方法将许可归还,AQS.state加1;归还之后,唤醒AQS队列中阻塞的线程获取许可。

<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并发控制示例 : 单许可信号量实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值