JAVA并发编程之Semnphore

JAVA并发编程之Semnphore

1.是什么?

Semaphore 通常我们叫它信号量,通常用于多个共享资源的互斥使用控制并发线程数

可以理解成抢车位,总共有十个车位,来一辆车占用一个,剩余车位数量减一,走一辆车,剩余车位就加一,如果十个车位全部被占满,后来的车只能等待,其他人把车开走。

2.使用场景

适用于资源复用的场景,比如数据库连接池

3.怎么用

以抢车位为例:

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);// 初始化信号量为3个停车位
        for(int i = 1; i <= 6; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire(); // 占车位
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
//                    System.out.println(Thread.currentThread().getName()+"占用车位3s");
                    semaphore.release(); // 释放车位
                    System.out.println(Thread.currentThread().getName()+"离开车位");
                }
            }, String.valueOf(i)).start();
​
        }
    }
}

 4.底层原理

①new Semaphore(int permits);创建一个sempare(),并传入共享资源的数量

public Semaphore(int permits) {
    // 底层new了一个非公平锁,并传入令牌数量(可以通过传入true、false指定是否公平),设置同步状态为permits
    sync = new NonfairSync(permits);
}

②NonfairSync作为semaphore的静态内部类存在

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;
​
    NonfairSync(int permits) {
        super(permits);
    }
​
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

③调用acquire()占用一个令牌

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 尝试占用共享资源(此方法使用了aqs中模板设计模式,前面分析过),tryAcquireShared(arg)最终会调用非公平锁的nonfairTryAcquireShared方法,去抢占令牌,如果抢占成功,拿着令牌继续执行,
    if (tryAcquireShared(arg) < 0)
    // 如果抢占失败(没有令牌了),就以共享模式进入阻塞队列,继任的节点尝试再次抢占,失败就阻塞,其他节点阻塞等待被唤醒(aqs框架提供的入队逻辑,前面分析过,不再赘述)
        doAcquireSharedInterruptibly(arg);
}

nonfairTryAcquireShared(int acquires)方法的主要逻辑就是只要还有令牌,就尝试去抢占(所以才是非公平的),一旦令牌没有了,才结束循环

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        // 用可使用的令牌数量减去占用的数量,获得占用后剩余令牌数
        int remaining = available - acquires;
        // 如果剩余令牌数小于0,说明令牌不足没法占用,直接返回剩余令牌数
        // 如果剩余令牌数>=0,也就是说令牌可以被占用,采用cas更新可被使用的令牌数量(同步状态),【多线程情况下可能被加塞(其他线程优先抢占了令牌),所以才使用自旋的方式去修改】
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

总结:==重要==

semaphore(共享锁)作为AQS框架的实现,也是只需要编写自己的如何占用和释放资源的业务逻辑就可以了,那么通过 nonfairTryAcquireShared(int acquires)方法看出,它并没有可重入的概念,如果多次调用acquire()方法,就会多次尝试占用令牌

问:semaphore初始化有10个令牌,一个线程重复调用11次acquire方法,会发生什么?

线程阻塞,不会继续往下运行。令牌没有重入的概念。你只要调用一次acquire方法,就需要有一个令牌才能继续运行。

④release()释放令牌

public void release() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    // 如果释放令牌成功,就执行唤醒工作
    if (tryReleaseShared(arg)) {
     // (之前在已经分析过)唤醒后继节点
        doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared(arg)

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        // 获取当前可提供的令牌数+要释放令牌数
        int next = current + releases;
        // 如果next<current,说明传入的释放数releases为负数,抛出异常
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
         // 否则尝试修改令牌数为释放后的令牌数,修改成功返回true,结束方法,失败自旋,继续尝试修改
        if (compareAndSetState(current, next))
            return true;
    }
}

总结:==重要==

我们通过tryReleaseShared(int releases)方法,可以看到,只要release一次令牌就会加+1个,即使已经超过了我们最初设置的令牌数

问:semaphore初始化有1个令牌,1个线程调用一次acquire方法,然后调用两次release方法,之后另外一个线程调用acquire(2)方法,此线程能够获取到足够的令牌并继续运行吗?

能,原因是release方法会添加令牌,并不会以初始化的大小为准。

问:semaphore初始化有2个令牌,一个线程调用1次release方法,然后一次性获取3个令牌,会获取到吗?

能,原因是release会添加令牌,并不会以初始化的大小为准

如有问题欢迎指正

面试题:Semaphore - 天宇轩-王 - 博客园

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值