【JUC-03】JUC—Semaphore核心知识讲解

1. 基础概念

Simaphore,第一次了解这个名词,从字面意思看:信号灯、信号量,总是不太清楚表示何种意思,并不能够清楚将该概念与程序结合起
来,直到后面看到《并发编程艺术》针对该名词讲解,颇为贴切,引用放在文首。

信号量,将其看做马路上控制流量的信号灯,比如”学府路“想要控制人流量,每次只允许200辆车通过,其余车辆必须在路口等待,前200辆车拿到通过的许可证,可以开进马路,后面的车因为没有许可证不允许驶入“学府路”。如果其中有10辆车已经完全通过马路,哨兵重新颁发10张许可证给等待车辆,这10辆车允许通过。

上面的例子中,车辆就是工作线程、许可证可以当做是令牌,驶入马路表示线程在执行,完全通过马路表示线程执行完毕。
通过上面的例子可以总结出Simaphore概念的含义:用于控制同时访问特定资源的线程数,并且每个线程在获取锁之前必须从semaphore获取许可(可以理解为令牌),当该线程获取锁之后执行完业务,将令牌释放回资源池中,其他线程可以重复利用。

信号量默认大小是1,表示只允许一个线程访问资源(也就是源码中用的permits,我理解翻译成资源比较合适),可以被用作互斥锁。这通常被称为二进制信号量,因为它只有两种状态:
还有一个资源可以被访问、零个资源可以被访问,

2. 类结构

Semaphore类本身只实现了一个Serializable接口,其内部有一个抽象内部类的Sync同步组件,该类本身继承了AbstractQueuedSynchronizer(以下都简称为AQS),以AQS为基础实现了自己的信号量同步器,之前提到过AQS中定义了通用的state,在Semaphore中表示可用资源数,Sync类本身提供了一些方法供其两个实现类NonfairSync和FairSync调用,保证Semaphore同样提供公平的获取资源及非公平的获取资源等两种模式,非公平模式下,资源利用的效率高于公平模式。

2.1 内部类同步器

2.1.1 Sync

信号量的同步器 主要利用AQS实现资源控制。state表示可以获取的资源数,本身有两种继承类,主要为表示公平、非公平获取资源模式。

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;

    /**
     * 允许访问的资源数
     * @param permits
     */
    Sync(int permits) {
        setState(permits);
    }

    /**
     * 获取允许访问的资源数
     * @return
     */
    final int getPermits() {
        return getState();
    }

    /**
     * 非公平、共享的获取资源数
     * 1. 首先获取所有可访问的子元素
     * 2. 查看是否够本次访问
     * 下次可用 = 当前可用 - acquires
     * @param acquires   尝试获取的资源数
     * @return   剩余可利用资源数
     */
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }

    /**
     * 释放资源数
     * 下次可用 = 当前可用 + releases
     * @param releases  释放资源的个数
     * @return  是否释放成功
     */
    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;
        }
    }

    /**
     * 减少资源数
     * 下次可用 = 当前可用 - reductions
     * @param reductions  减少的资源个数
     */
    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;
        }
    }

    /**
     * 将可用资源数降至为0
     * @return  当前可用资源数
     */
    final int drainPermits() {
        for (;;) {
            int current = getState();
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}

2.1.2 NonfairSync

Sync的非公平静态内部类,无论是构造方法还是获取资源的方法都是直接调用父类的方法,本身只是提供一种模式的保证。

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

    /**
     * 初始化可用资源数  主要调用父类构造方法
     * @param permits
     */
    NonfairSync(int permits) {
        super(permits);
    }

    /**
     * 非公平下获取资源数
     * @param acquires  获取资源数
     * @return
     */
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

2.1.3 FairSync

Sync的公平静态内部类

static final class FairSync extends Sync {
    private static final long serialVersionUID = 2014338818796000944L;

    /**
     * 初始化可用资源数  主要调用父类构造方法
     * @param permits
     */
    FairSync(int permits) {
        super(permits);
    }

    /**
     * 公平的获取资源数
     * 1. 调用AQS方法判断,当前是否有等待线程在获取资源,如果有,则为了公平起见,暂时不能获取资源
     * 2. 直到第一个条件不满足,查询可用资源数
     * 3. 如果可用资源数大于零并且通过CAS设置可用资源成功,则直接返回,如果可用资源不足,直接返回。
     * @param acquires  尝试获取的资源数
     * @return
     */
    protected int tryAcquireShared(int acquires) {
        for (;;) {
            if (hasQueuedPredecessors())
                return -1;
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 || compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

2.2 构造方法

  1. 初始化可用资源数。默认采用非公平模式
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
  1. 初始化可用资源数。可以自定义是否是公平模式
/**
 * 初始化信号量对象
 * @param permits   允许访问资源数
 * @param fair      是否公平
 */
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

3. 基本方法

1. acquire()

获取可用资源。该方法直接调用AQS的共享的、可中断的获取同步状态的方法。
如果获取不到资源则阻塞线程,直到有可用资源或者被其他线程中断,获取到资源之后,将资源池的可用资源数减1

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

2. acquireUninterruptibly()

非中断的获取资源。该方法直接调用AQS中的acquireShared()方法
如果没有资源可访问,则当前线程会一直等待,直到其他线程释放资源,资源池中有资源可访问。

public void acquireUninterruptibly() {
    sync.acquireShared(1);
}

3. tryAcquire()

尝试获取资源,非公平的抢占,不在乎是否有线程在等待。如果资产池有中资源可用则返回true,否则返回false
直接调用抽象内部类的非公平抢占式方法

public boolean tryAcquire() {
    return sync.nonfairTryAcquireShared(1) >= 0;
}

4. tryAcquire(long timeout, TimeUnit unit)

如果在给定时间内有可用获取资源或者线程没有被中断,则线程尝试获取资源。 公平机制
本质是调用AQS中的tryAcquireSharedNanos(int arg, long nanosTimeout)
如果获取资源成功,则返回true,并且将资源池可用资源减1
如果没有可用资源线程会一直等待,直到出现以下三种情况:
1. 其他线程释放资源,并且当前线程是下一个等着获取资源的线程
2. 其他线程中断当前现车个
3. 超时

/**
 * @param timeout     超时时间
 * @param unit        超时时间单位
 * @return
 * @throws InterruptedException
 */
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

5. release()

释放资源,资源池可用资源+1
方法实现是直接调用AQS中的releaseShared(int arg)方法

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

6. acquire(int permits)

获取特定数量资源 调用AQS中的acquireSharedInterruptibly(int arg)方法

  1. 获取给定数量资源,资源不够则阻塞等待,如果线程被中断则放弃等待
    其中permits表示可获取的资源
public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
}

7. acquireUninterruptibly(int permits)

获取特定数量资源(不可中断)
在可用资源够用之前,需要一直等待获取,即使当前线程被其他线程中断,也会一直阻塞等待

public void acquireUninterruptibly(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireShared(permits);
}

8. tryAcquire(int permits)

获取特定数量的资源数 非公平的获取
如果资源数不够,则直接返回false,放弃等待

public boolean tryAcquire(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    return sync.nonfairTryAcquireShared(permits) >= 0;
}

9. tryAcquire(int permits, long timeout, TimeUnit unit)

带有超时的尝试获取特定资源数
本质是调用AQS中的tryAcquireSharedNanos(int arg, long nanosTimeout)方法

public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}

10. release(int permits)

释放特定资源数

public void release(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.releaseShared(permits);
}

11 其他通用方法

/**
 * 可以利用的资源数
 * @return
 */
public int availablePermits() {
    return sync.getPermits();
}

/**
 * 获取所有可利用的资源数,并将其降为0
 * @return
 */
public int drainPermits() {
    return sync.drainPermits();
}

/**
 * 降低可用资源数
 * @param reduction
 */
protected void reducePermits(int reduction) {
    if (reduction < 0) throw new IllegalArgumentException();
    sync.reducePermits(reduction);
}

/**
 * 判断当前获取资源的模式是否是公平模式   公平模式返回true,非公平模式返回false
 * @return
 */
public boolean isFair() {
    return sync instanceof FairSync;
}

/**
 * 查询是否有线程在等待获取资源
 * 同样是用于系统的监控
 * @return
 */
public final boolean hasQueuedThreads() {
    return sync.hasQueuedThreads();
}

/**
 * 查询等待队列中等待线程数(估计值)
 * 该方法同样用于监控系统的状态
 * @return
 */
public final int getQueueLength() {
    return sync.getQueueLength();
}

/**
 * 查询所有等待队列中等待线程的集合
 * 该方法同样用于监控系统的状态
 * @return
 */
protected Collection<Thread> getQueuedThreads() {
    return sync.getQueuedThreads();
}

4. 实例

设置30个正在执行的线程,但是每次只允许10个并发线程执行,5s之后执行问,其他线程可以获取资源。

/**
 * @time 2019/12/14 14:54
 * @Description 信号量的例子
 *
 * 设置30个正在执行的线程,但是只允许10个并发线程执行
 */
public class SemaphoreTest {

    private static final int THREAD_TOTAL = 30;

    /**
     * 初始化一个固定的线程池
     */
    private static ExecutorService poolExecutor = new ThreadPoolExecutor(THREAD_TOTAL,
            THREAD_TOTAL,
            60,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue <>(1024),
            new ThreadPoolExecutor.AbortPolicy());

    /**
     * 初始化信号量
     */
    static Semaphore semaphore = new Semaphore(10,false);

    public static void main(String[] args) {
        for(int i = 0; i < THREAD_TOTAL; i++){
            poolExecutor.execute(()-> {
                try {
                    semaphore.acquire();
                    System.out.println("允许获取资源,序号");
                    // 模拟工作1S
                    Thread.sleep(5000);
                    // 释放资源,然后
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        poolExecutor.shutdown();
    }
}

5. 总结

Semaphore作用类似于开头所说,用于控制线程访问特定的资源,因此经常用与限流框架中、比如hystrix框架。

6. 参考资料

  1. 并发编程艺术
  2. https://docs.oracle.com/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值