笔记 9 · Semaphore全面分析

Semaphore,即信号量,是一种用于控制线程对共享资源访问的同步工具。它通过计数器管理许可,允许多个线程在不超过预设数量的情况下并发访问。Semaphore提供公平和非公平两种模式,适用于如数据库连接池、限流等场景。在Java中,Semaphore通过AbstractQueuedSynchronizer实现,内部维护了一个许可计数和等待线程队列。acquire和release方法分别用于获取和释放许可,确保线程安全地访问资源。

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

Semaphore 是什么

Semaphore 通常叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。其内部是用一个计数器控制对共享资源的访问,数据大于0,资源允许访问,等于0是拒绝访问共享资源的,在访问共享资源前要先调用acquire方法,如果计数据大于0则可以访问资源,访问后要调用release方法释放共享资源,如果等于0则调用线程阻塞于此方法。

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

使用场景

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

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

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

方法

public Semaphore(int permits){ sync = new NonfairSync(permits); }

  • 创建一个给定许可数的非公平的Semaphore。
  • permits:允许同时并行的线程数。

public Semaphore(int permits, boolean fair){ … }

  • 创建Semaphore,可以设定许可数和同步方式(fair = true 公平竞争; = false 非公平)
  • fair = true,在调用aquire时先检查下队列中有没有等待的线程,有等待的线程就要进入队列等待,也就是保证了先到先得。
  • fair = false,在调用aquire时是需要检查下队列中有没有等待的线程,也就是后来者可以先获得许可。

private void acquire() throws InterruptedException;

  • 获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。

public void acquire(int permits) throws InterruptedException;

  • 获取指定数量的令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态

private void acquireUninterruptibly();

  • 获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。

private boolean tryAcquire() ;

  • 尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。

private boolean tryAcquire(long timeout, TimeUnit unit);

  • 尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。

private void release();

  • 释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。

private final boolean hasQueuedThreads();

  • 等待队列里是否还存在等待线程。

private final int getQueueLength();

  • 获取等待队列里阻塞的线程数。

private int drainPermits();

  • 清空令牌把可用令牌数置为0,返回清空令牌的数量。

private int availablePermits();

  • 返回可用的令牌数量。

#成员变量
private final ReentrantLock lock = new ReentrantLock();

  • 起到多重屏障的作用。Barrier被冲破或重置的时候,generation会发生变化。

案例

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

 业务场景 :
 1、停车场容纳总停车量10。
 2、当一辆车进入停车场后,显示牌的剩余车位数响应的减1.
 3、每有一辆车驶出停车场后,显示牌的剩余车位数响应的加1。
 4、停车场剩余车位不足时,车辆只能在外面等待。
 **/
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(() -> {
                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(1000));//模拟车辆在停车场停留的时间
                    System.out.println(Thread.currentThread().getName() + "驶出停车场");
                    semaphore.release();//释放令牌,腾出停车场车位
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, i + "号车");
            thread.start();
        }
    }
}

源码分析

  1. 分析下Semaphore类的源码,首先可以肯定一个简单的计数器是不能实现同步的,因为计数器本身的同步也是需要解决的。
  2. Semaphore是其通一个链式的queue来实现的AbstractQueuedSynchronizer,其中一个成员是
    private volatile int state
  3. 调用acquire方法会检查是否大于0,调用release方法会使state加1,.
  4. AbstractQueuedSynchronizer其中有一个内部类Node,Node有一个成员Thread,这个成员就是存储调acquire阻塞时的线程,所以AbstractQueuedSynchronizer其实是一个包含计数器的,线程链式队列。

首先从构造函数出发

# 该构造函数会创建具有给定的许可数(permits)和非公平(new NonfairSync())的公平设置的Semaphore。
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

# 该构造函数会创建具有给定的许可数(permits)和给定的公平设置(fair)的Semaphore。    
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

内部类

说明:Semaphore与ReentrantLock的内部类的结构相同,类内部总共存在Sync、NonfairSync()、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。下面逐个进行分析。

Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
    Sync(int permits) {
        # 设置状态数,设置给AQS中的成员变量  private volatile int state;
        setState(permits);
    }
    
    # 获取许可,从AQS中获取
    final int getPermits() {
        return getState();
    }

    // 共享模式下非公平策略获取
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            // 获取许可数
            int available = getState();
            // 剩余的许可
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining)) // 许可小于0或者比较并且设置状态成功
                return remaining;
        }
    }
    
    // 共享模式下进行释放
    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;
        }
    }

    // 根据指定的缩减量减小可用许可的数目
    final void reducePermits(int reductions) {
        for (;;) { // 无限循环
            // 获取许可
            int current = getState();
            // 可用的许可
            int next = current - reductions;
            if (next > current) // underflow
                throw new Error("Permit count underflow");
            if (compareAndSetState(current, next)) // 比较并进行设置成功
                return;
        }
    }

    // 获取并返回立即可用的所有许可
    final int drainPermits() {
        for (;;) { // 无限循环
            // 获取许可
            int current = getState();
            if (current == 0 || compareAndSetState(current, 0)) // 许可为0或者比较并设置成功
                return current;
        }
    }
}
NonfairSync

NonfairSync类继承了Sync类,表示采用非公平策略获取资源,其只有一个tryAcquireShared方法,重写了AQS的该方法,其源码如下。

static final class NonfairSync extends Sync {
    NonfairSync(int permits) {
        # super  指向 Sync的构造方法,设置给AQS中的成员变量  private volatile int state;
        super(permits);
    }
    # 共享模式下获取
    protected int tryAcquireShared(int acquires) {
    	# 调用父类Sync的nonfairTryAcquireShared方法,表示按照非公平策略进行资源的获取。
        return nonfairTryAcquireShared(acquires);
    }
}
FairSync

FairSync类继承了Sync类,表示采用公平策略获取资源,它会判断同步队列中是否存在其他的等待节点。其只有一个tryAcquireShared方法,重写了AQS的该方法,其源码如下。

static final class FairSync extends Sync {
    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)) // 剩余的许可小于0或者比较设置成功
                return remaining;
        }
    }
}

再看 acquire()

# 此方法从信号量获取一个(多个)许可,在提供一个许可前一直将线程阻塞,或者线程被中断。
public void acquire() throws InterruptedException {
   # 调用AbstractQueuedSynchronizer.class 中的方法
   sync.acquireSharedInterruptibly(1);
}

# 获取指定数量的许可
public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
}

最后看 release()

# 释放一个(多个)许可,将其返回给信号量
public void release() {
   # 调用AbstractQueuedSynchronizer.class 中的方法
   sync.releaseShared(1);
}
    
public void release(int permits) {
   if (permits < 0) throw new IllegalArgumentException();
    sync.releaseShared(permits);
}

AQS

# The synchronization state.
private volatile int state;

protected final void setState(int newState) {
    state = newState;
}

# 返回同步状态的当前值。
protected final int getState() {
   return state;
}

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

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

在这里插入图片描述

注:
基础内容主要来源勤劳的小手
源码分析主要来源leesf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值