Semaphore信号量的使用与源码分析、使用wait-notify手写实现

Semaphore(信号量)是用来限制资源(如线程)的并发数量。

api

方法名说明
void acquire()从信号量获取1个许可证,一直阻塞到有许可证或被中断
void acquire(int permits)从信号量获取permits个许可证,一直阻塞到有许可证或被中断
void acquireUninterruptibly()从信号量获取1个许可证,一直阻塞到有许可证,不可被中断
void acquireUninterruptibly(int permits)从信号量获取permits个许可证,一直阻塞到有许可证,不可被中断
int availablePermits()返回此信号量中当前可用的许可证数
int drainPermits()获取并返回所有可立即获得的许可证
int getQueueLength()返回等待获取许可证的线程数
boolean hasQueuedThreads()返回是否有等待获取许可证的线程
boolean isFair()如果此信号量的公平设置为真,则返回true,否则为false
void release()释放许可证,将其返回到信号量
void release(int permits)释放给定数量的许可证,将其返回到信号量
boolean tryAcquire()从这个信号量尝试获得许可证,不阻塞
boolean tryAcquire(int permits)从这个信号量尝试获得给定数量的许可证,不阻塞
boolean tryAcquire(int permits, long timeout, TimeUnit unit)在给定的等待时间内从该信号量尝试获取给定数量的许可证
boolean tryAcquire(long timeout, TimeUnit unit)在给定的等待时间内从该信号量尝试获取1个许可证

简单使用

模拟场景:10个线程同时存储数据到数据库,而数据库的连接数只有3个,这时我们必须控制只有3个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。

package com.morris.concurrent.tool.semaphore.api;

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

public class SemaphoreDemo {

    private static Semaphore semaphore = new Semaphore(3);
    private static ExecutorService executor = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            executor.execute(() -> {

                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "当前等待的线程数:" + semaphore.getQueueLength());
                    System.out.println(Thread.currentThread().getName() + "当前可用的凭证数:" + semaphore.availablePermits());
                    System.out.println("save data");
                    TimeUnit.SECONDS.sleep(1);
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            });
        }
        executor.shutdown();
    }

}

运行结果如下:

pool-1-thread-2当前等待的线程数:0
pool-1-thread-2当前可用的凭证数:2
save data
pool-1-thread-3当前等待的线程数:0
pool-1-thread-3当前可用的凭证数:1
save data
pool-1-thread-4当前等待的线程数:0
pool-1-thread-4当前可用的凭证数:0
save data
pool-1-thread-7当前等待的线程数:6
pool-1-thread-7当前可用的凭证数:1
save data
pool-1-thread-1当前等待的线程数:5
pool-1-thread-1当前可用的凭证数:0
save data
pool-1-thread-5当前等待的线程数:4
pool-1-thread-5当前可用的凭证数:0
save data
pool-1-thread-6当前等待的线程数:3
pool-1-thread-8当前等待的线程数:2
pool-1-thread-8当前可用的凭证数:0
save data
pool-1-thread-6当前可用的凭证数:0
save data
pool-1-thread-10当前等待的线程数:1
pool-1-thread-10当前可用的凭证数:0
save data
pool-1-thread-9当前等待的线程数:0
pool-1-thread-9当前可用的凭证数:1
save data

综合使用

可见Semaphore的构造方法中的许可证个数只是一个初始值,可以通过release动态添加许可证。

package com.morris.concurrent.tool.semaphore.api;

import java.util.concurrent.Semaphore;

public class SemaphoreComplexDemo {

    public static void main(String[] args) throws InterruptedException {

        Semaphore semaphore = new Semaphore(10);

        semaphore.acquire(2); // 一次获取多个凭证
        semaphore.acquire(2);
        semaphore.acquire(2);
        semaphore.acquire(2);
        semaphore.acquire(2);

        System.out.println(semaphore.availablePermits());

        semaphore.release(10); // 动态添加凭证
        System.out.println(semaphore.availablePermits());

        semaphore.release(6);
        System.out.println(semaphore.availablePermits());


        semaphore.drainPermits(); // 获取所有的凭证
        System.out.println(semaphore.availablePermits());
    }
}

总结

  • Semaphore并不能保证acquire()和release()中间代码的原子性,如果有共享资源的话,在高并发下可能会线程安全问题。

  • 所谓公平就是获取许可证的顺序与调用acquire的顺序有关,谁先调用acquire谁先获得许可证,但是不能百分之百保证,仅仅在概率上保证。

源码分析

构造方法

java.util.concurrent.Semaphore#Semaphore(int)

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

java.util.concurrent.Semaphore.NonfairSync#NonfairSync

static final class NonfairSync extends Sync {

    NonfairSync(int permits) {
        super(permits);
    }
... ...

java.util.concurrent.Semaphore.Sync#Sync

abstract static class Sync extends AbstractQueuedSynchronizer {

    Sync(int permits) {
        setState(permits);
    }
... ...

Semaphore底层使用了AQS,并将AQS中state的初始值设置为permits,也就是state为资源的数量。

acquire

java.util.concurrent.Semaphore#acquire()

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

这里调用了AQS中获取共享状态的方法acquireSharedInterruptibly(),acquireSharedInterruptibly()中使用了模板方法模式,会调用子类Semaphore.NonfairSync.tryAcquireShared()。

java.util.concurrent.Semaphore.NonfairSync#tryAcquireShared

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

java.util.concurrent.Semaphore.Sync#nonfairTryAcquireShared

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
 // 扣减获取的许可凭证书数
		// remaining>=0表示成功,remaining < 0表示失败
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

当许可凭证数足够时,那么tryAcquireShared就会返回大于等于0,然后线程往下执行,当许可凭证数不足时,就会进入AQS中的同步队列中进行等待,直到被其他线程唤醒。

release

java.util.concurrent.Semaphore#release()

public void release() {
    sync.releaseShared(1);
}

这里调用了AQS中释放共享状态的方法releaseShared(),releaseShared()中也使用了模板方法模式,会调用子类Semaphore.NonfairSync.tryReleaseShared()。

java.util.concurrent.Semaphore.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");
        if (compareAndSetState(current, next))
            return true;
    }
}

tryReleaseShared会释放许可凭证数量,然后唤醒同步队列中等待的线程。

手写实现

package com.morris.concurrent.tool.semaphore.my;

import java.util.*;

/**
 * 使用wait-notify实现Semaphore
 */
public class WaitNotifySemaphore {

    private int permits;

    private Set<Thread> waitThreads = new HashSet<>(); // 等待的线程数

    public WaitNotifySemaphore(int permits) {
        this.permits = permits;
    }

    public synchronized void acquire() throws InterruptedException {
        while (permits == 0) {
            waitThreads.add(Thread.currentThread());
            this.wait();
        }
        waitThreads.remove(Thread.currentThread());
        permits--;
        this.notifyAll();
    }

    public synchronized void release() {
        permits++;
        this.notifyAll();
    }

    public int availablePermits() {
        return permits;
    }

    public boolean hasQueuedThreads() {
        return waitThreads.isEmpty();
    }

    public int getQueueLength() {
        return waitThreads.size();
    }

    protected Collection<Thread> getQueuedThreads() {
        return Collections.unmodifiableSet(waitThreads);
    }

    public String toString() {
        return super.toString() + "[Permits = " + permits + "]";
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

morris131

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值