并发工具之Semaphore

参考: https://blog.csdn.net/javazejian/article/details/76167357
1.semaphore 也就是我们常说的信号灯,用来控制并发时线程的数量,实现了序列化接口。
通过 acquire 获取一个许可,如果没有就等待;
通过 release 释放一个许可。有点类似限流的作用。
Semaphore 只有3个操作:
(1).初始化;Semaphore 构造函数,传入信号数目,Semaphore 将信号数目设置到 AQS 的 state 上;
(2).acquire 获取信号,如果信号数目大于0,通过 CAS 将信号数减1, 获取成功;否则被阻塞,并将线程记录到 AQS 的等待锁队列中;
(3).release 释放信号,通过 CAS 将信号数加1,并唤醒等待队列上队首线程重新尝试获取信号。

2.semaphore 实现原理
Semaphore 内部其实是根据同步状态的值来限制并发的时线程的数量,当同步状态值为0时,后来的线程将被阻塞,直到有线程释放。
Semaphore 的锁是通过 Sync 这个类完成的,Sync 则继承自 AQS ,AQS 是独占锁和共享锁的父类,通过继承 AQS 实现共享锁。
Sync 有两个子类是 FairSync 和 NonfairSync,分别代表公平锁和非公平锁。
FairSync 判断是否有线程等待,如果没有则尝试获取信号,如果有则加入到等待队列。
NonfairSync 则先尝试获取锁,如果获取失败则再加入等待队列。
Semaphore 默认是非公平锁
为什么使用非公平锁,这个是性能上的考虑,如果每次都去唤醒线程去获取信号,这是非常消耗资源的,非公平锁的性能和吞吐量也明显优于公平锁。

3.java.util.concurrent.Semaphore#acquire()
Semaphore 调用 acquire方法时,调用 AQS 的 acquireSharedInterruptibly 方法,AQS 则调用子类的 tryAcquireShared 方法,如果获取成功,则直接返回;如果获取失败,则调用调用 AQS 的方法 doAcquireSharedInterruptibly 阻塞并加入到等待队列等待唤醒。

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

4.java.util.concurrent.Semaphore#release()
Semaphore 调用 release 方法时,调用 AQS 的 releaseShared 方法,AQS 则调用子类的 tryReleaseShared 方法,如果释放成功,则调用 doReleaseShared 方法唤醒等待队列队首线程,线程启动后,如果 tryAcquireShared 返回值大于等于 0,则通过 setHeadAndPropagate 方法进行传播,唤醒下一个线程。

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

总结:
Semaphore获取许可(调用acquire()方法),最终会调用到 AQS中的方法,操作的是 state 的值减1,如果获取许可失败则进入阻塞队列;
Semaphore释放许可(调用release()方法),最终也是进入 AQS中的方法,操作 state加1,如果调用成功则会唤醒阻塞队列队首线程。

抢占锁:实际上就是进行CAS操作,尝试改变 AQS 中 state 的值。
公平锁和非公平锁的区别:先判断队列是否有元素再尝试获取锁则是公平的;上来就尝试抢占锁就是非公平的;

共享锁:资源不足时,线程将被封装共享模式的Node结点加入同步队列等待。共享模式,允许多个线程同时获取同步状态
java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;

Semaphore实现互斥锁:在初始化信号量时传入1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。

无锁操作:for死循环加CAS操作,保证线程安全。

使用示例:

package com.cmz.concurrent;

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

/**
 * @author chen.mz
 * @email 1034667543@qq.com
 * @nickname 陈梦洲
 * @date 2019/9/10
 * @description Semaphore Demo
 * <p>信号量:常用于限制可以访问某些资源的线程数量</p>
 * <p>可以理解为:我们的停车场最多允许停 5 辆车,那么当 semaphore.acquire() 发放完毕,其他的车辆进入不了(线程就阻塞)</p>
 * <p>当有车辆离开时会归还停车卡 semaphore.release(),此时阻塞的就可以被唤醒,可以继续进入停车场</p>
 */
public class SemaphoreDemo {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量,只允许 3个线程同时访问
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 10; i++) {
            final long num = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 获取许可(拿到停车卡)
                        semaphore.acquire();
                        // do something
                        System.out.println("Accessing: " + num);
                        Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
                        // 释放许可(还回停车卡)
                        semaphore.release();
                        System.out.println("Release..." + num);
                    } catch (InterruptedException  e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值