详解Java信号量Semaphore的原理及使用

目录
  • 1.Semaphore的概述
  • 2.Semaphore的原理
    • 2.1 基本结构
    • 2.2 可中断获取信号量
    • 2.3 不可中断获取信号量
    • 2.4 超时可中断获取信号量
    • 2.5 尝试获取信号量
    • 2.6 释放信号量
  • 3.Semaphore的使用
  • 4.Semaphore的总结

1.Semaphore的概述

public class Semaphore extends Object implements Serializable

Semaphore来自于JDK1.5的JUC包,直译过来就是信号量,被作为一种多线程并发控制工具来使用。

Semaphore可以控制同时访问共享资源的线程个数,线程通过 acquire方法获取一个信号量,信号量减一,如果没有就等待;通过release方法释放一个信号量,信号量加一。它通过控制信号量的总数量,以及每个线程所需获取的信号量数量,进而控制多个线程对共享资源访问的并发度,以保证合理的使用共享资源。相比synchronized和独占锁一次只能允许一个线程访问共享资源,功能更加强大,有点类似于共享锁!

2.Semaphore的原理

2.1 基本结构

根据uml类图,可以很明显的看出来Semaphore和CountDownLatch一样都是直接使用AQS实现的。区别就是Semaphore还分别实现了公平模式FairSync和非公平模式NonfairSync两个内部类。

实际上公平与非公平只是在获取信号量的时候得到体现,它们的释放信号量的方法都是一样的,这就类似于ReentrantLock:公平与非公平只是在获取锁的时候得到体现,它们的释放锁的方法都是一样的!或许这里有人在想,信号量是不是可以看作锁资源呢?某些时候这么看是没问题的,比如都是获取了只有获取了“信号量”或者“锁”才能访问共享资源,但是它们又有区别,锁资源会和线程绑定,而信号量则不会和线程绑定。

在构造器部分,如同CountDownLatch 构造函数传递的初始化计数个数count被赋给了AQS 的state 状态变量一样,Semaphore的信号量个数permits同样赋给了AQS 的state 值。

在创建Semaphore时可以使用一个fair变量指定是否使用公平策略,默认是非公平的模式。公平模式会确保所有等待的获取信号量的线程按照先进先出的顺序获取信号量,而非公平模式则没有这个保证。非公平模式的吞吐量比公平模式的吞吐量要高,而公平模式则可以避免线程饥饿。

/**
 * 保存某个AQS子类实例
 */
private final Sync sync;

/**
 * 创建具有给定的信号量数和非公平的公平模式的 Semaphore。
 *
 * @param permits 初始的可用信号量数目。此值可能为负数,在这种情况下,必须在授予任何获取信号量前进行释放信号量。
 */
public Semaphore(int permits) {
    //默认初始化NonfairSync实例
    sync = new NonfairSync(permits);
}

/**
 * 创建具有给定的信号量数和给定的公平设置的 Semaphore。
 *
 * @param permits 初始的可用信号量数目。此值可能为负数,在这种情况下,必须在授予任何获取信号量前进行释放信号量。
 * @param fair    如果此信号量保证在争用时按先进先出的顺序授予信号量,则为 true;否则为 false。
 */
public Semaphore(int permits, boolean fair) {
    //根据fair参数选择初始化一个公平FairSync类或者非公平NonfairSync类的实例
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

/**
 * 非公平模式的实现
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

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

    //…………其他方法后面再讲

}

/**
 * 公平模式的实现
 */
static final class FairSync extends Sync {
    private static final long serialVersionUID = 2014338818796000944L;

    FairSync(int permits) {
        super(permits);
    }

    //…………其他方法后面再讲

}

/**
 * 信号量的同步实现。 使用 AQS 的state状态表示信号量。子分类为公平和非公平模式。
 */
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;

    /**
     * 构造器
     *
     * @param permits 初始的可用信号量数目。
     */
    Sync(int permits) {
        //被设置为state值
        setState(permits);
    }

    //…………其他方法后面再讲
}

2.2 可中断获取信号量

public void acquire()

可中断的获取一个信号量,没有则一直阻塞,直到在其他线程提供信号量并唤醒该线程或者线程被中断。获取一个信号量就立即返回,将可用的信号量数减 1。 如果调用此方法时已被中断或者等待时被中断,则抛出 InterruptedException,并且清除当前线程的已中断状态。

public void acquire(int permits)

可中断的获取permits 个信号量。

内部调用AQS的acquireSharedInterruptibly方法,这实际上就是共享式可中断获取资源的模版方法,因此Semaphore和CountDownLatch一样都是基于共享资源模式。

/**
 * Semaphore的acquire方法
 * 从信号量获取一个信号量,没有则一直阻塞,直到在其他线程提供信号量并唤醒或者线程被中断。
 *
 * @throws InterruptedException 如果调用此方法时已被中断或者等待时被中断
 */
public void acquire() throws InterruptedException {
    //内部调用AQS的acquireSharedInterruptibly方法
    //这实际上就是共享式可中断获取资源模版方法
    sync.acquireSharedInterruptibly(1);
}

/**
 * 从信号量获取permits个信号量,没有则一直阻塞,直到在其他线程提供信号量并唤醒或者线程被中断。
 *
 * @param permits 需要获取的信号量数量
 * @throws InterruptedException 如果调用此方法时已被中断或者等待时被中断
 */
public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    //参数就是permits
    sync.acquireSharedInterruptibly(permits);
}

/**
 1. AQS的acquireSharedInterruptibly方法
 2. 共享式可中断获取信号量资源的模版方法
 3.  4. @param arg 需要获取的信号量资源数量
 5. @throws InterruptedException 如果调用此方法时已被中断或者等待时被中断
 */
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    //最开始就检查一次,如果当前线程是被中断状态,直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //调用tryAcquireShared尝试获取共享信号量资源,这个方法是子类自己重写的
    //如果返回值小于0,表示当前线程共享信号量资源失败,否则表示成功
    //Semaphore的FairSync和NonfairSync对tryAcquireShared分别做出了公平和不公平的实现
    if (tryAcquireShared(arg) < 0)
        //获取不到就执行doAcquireSharedInterruptibly方法
        doAcquireSharedInterruptibly(arg);
}

在获取共享信号量资源的时候,Semaphore还实现了公平模式和非公平模式!它们的实现实际上和lock锁的实现中锁资源的公平、非公平获取非常类似!

2.1.1 公平模式

公平模式调用FairSync的tryAcquireShared方法!

如果我们学习了AQS、ReentrantLock、ReadWriteLock的源码,我们第一个就会发现hasQueuedPredecessors方法,这个方法是AQS为实现公平模式的预定义的方法,AQS帮我们实现好了,该方法用于查询是否有任何线程等待获取信号量资源的时间超过当前线程。

大概步骤为:

  • 开启一个死循环:
  • 调用hasQueuedPredecessors方法,判断是否有线程比当前线程更早地请求获取信号量资源。如果该方法返回true,则表示有线程比当前线程更早地请求获取信号量资源,由于是公平的的,因此当前线程不应该获取信号量资源,直接返回-1,表示获取信号量资源失败。
  • 到这里还没有返回,表示当前线程就是最早请求获取信号量资源,可以尝试获取。
  • 获取state的值available,我们知道state代表信号量资源数量。remaining为available减去需要获取的信号量资源数量之后的差值。
  • 如果remaining小于0,那么返回remaining值,由于是负数,因此获取失败,如果大于等于0,那么表示可以获取成功,尝试CAS的更新state,更新成功之后同样返回remaining,由于是大于等于0的数,因此获取成功。
  • 如果remaining大于等于0,但是CAS更新state失败,那么循环重试。

原理还是很简单的,就是判断目前的信号量资源数量—state的值,是否满足要获取的信号量资源数量,acquire()方法默认获取1个资源。获取到了就是CAS的原子性的将state递减,否则表示获取资源失败,那么可能会阻塞。但是我们也会发现:如果remaining大于等于0,但是CAS更新state失败,那么会循环重试,这里为什么要重试呢?

实际上我们的在AQS文章的“可重入共享锁的实现” 部分已经讲过:因为可能会有多个线程同时获取信号量资源,但是由于CAS只能保证一次只有一个线程成功,因此其他线程必定失败,但此时,实际上还是存在剩余的信号量资源没有被获取完毕的,因此让其他线程重试,相比于直接加入到同步队列中,对于信号量资源的利用率更高!

/**
 * 公平模式
 */
static final class FairSync extends Sync {
    /**
     * 尝试公平的获取共享信号量资源
     *
     * @param acquires 获取信号量资源数量
     * @return 如果返回值小于0,表示当前线程共享信号量资源失败,否则表示成功
     */
    protected int tryAcquireShared(int acquires) {
        /*开启一个循环尝试获取共享信号量资源*/
        for (; ; ) {
            //这是AQS实现公平模式的预定义的方法,AQS帮我们实现好了。该方法用于查询是否有任何线程等待获取信号量资源的时间超过当前线程
            //如果该方法返回true,则表示有线程比当前线程更早地请求获取信号量资源。由于是公平的的,因此当前线程不应该获取信号量资源,直接返回-1,表示获取信号量资源失败
            if (hasQueuedPredecessors())
                return -1;
            //到这里,表示当前线程就是最早请求获取信号量资源,可以尝试获取

            //获取state的值available,我们知道state代表信号量资源数量
            int available = getState();
            //remaining为available减去需要获取的信号量资源数量之后的差值
            int remaining = available - acquires;
            //如果remaining小于0,那么返回remaining值,由于是负数,因此获取失败
            //如果大于等于0,那么表示可以获取成功,尝试CAS的更新state,更新成功之后同样返回remaining,由于是大于等于0的数,因此获取成功
            if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                return remaining;
            //如果remaining大于等于0,但是CAS更新state失败,那么循环重试
        }
    }
}

2.1.2 非公平模式

非公平模式调用NonfairSync的tryAcquireShared方法!

相比于公平模式的实现,少了hasQueuedPredecessors的判断。可以想象:如果某线程A 先调用了aquire()方法获取信号量,但是如果当前信号量个数为0,那么线程A 会被放入AQS 的同步队列阻塞。

过一段时间后线程B调用了release()方法释放了一个信号量,他它会唤醒队列中等待的线程A,但是这时线程C又调用了aquire()方法。如果采用非公平策略,那么线程C就会和线程A 去竞争这个信号量资源。由nonfairTryAcquireShared的代码可知,线程C完全可以在线程A 被激活前,或者激活后先于线程A 获取到该信号量,也就是在这种模式下阻塞线程和当前请求的线程是竞争关系,而不遵循先来先得的策略。

另外,非公平模式的具体实现是在父类Sync中的nonfairTryAcquireShared方方法,为什么该方法要实现在父类中的,因为无论是指定的公平模式还是非公平模式,它们的tryAcquire方法都是调用的nonfairTryAcquireShared方法,即非公平的,因此实现在父类中!

/**
 * 非公平模式
 */
static final class NonfairSync extends Sync {

    /**
     * 尝试非公平的获取共享信号量资源
     *
     * @param acquires 获取信号量资源数量
     * @return 如果返回值小于0,表示当前线程共享信号量资源失败,否则表示成功
     */
    protected int tryAcquireShared(int acquires) {
        //调用父类Sync的nonfairTryAcquireShared方法
        //为什么该方法要实现在父类中的,因为无论是指定的公平模式还是非公平模式,
        //它们的tryAcquire方法都是调用的nonfairTryAcquireShared方法,即非公平的,因此实现在父类中
        return nonfairTryAcquireShared(acquires);
    }
}

/**
 * AQS的实现,作为公平和非公平模式的父类,有一些共享方法
 */
abstract static class Sync extends AbstractQueuedSynchronizer {

    /**
     * 尝试非公平的获取共享信号量资源
     *
     * @param acquires 获取信号量资源数量
     * @return 如果返回值小于0,表示当前线程共享信号量资源失败,否则表示成功
     */
    final int nonfairTryAcquireShared(int acquires) {
        /*开启一个循环尝试获取共享信号量资源*/
        for (; ; ) {
            //相比于公平模式,少了hasQueuedPredecessors的实现
            //获取state的值available,我们知道state代表信号量资源数量
            int available = getState();
            //remaining为available减去需要获取的信号量资源数量之后的差值
            int remaining = available - acquires;
            //如果remaining小于0,那么返回remaining值,由于是负数,因此获取失败
            //如果大于等于0,那么表示可以获取成功,尝试CAS的更新state,更新成功之后同样返回remaining,由于是大于等于0的数,因此获取成功
            if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                return remaining;
            //如果remaining大于等于0,但是CAS更新state失败,那么循环重试
        }
    }
}

2.3 不可中断获取信号量

public void acquireUninterruptibly()

不可中断的获取一个信号量,没有则一直阻塞,直到在其他线程提供信号量并唤醒该线程。获取一个信号量就立即返回,将可用的信号量数减 1。

相比于acquire()方法,该方法不响应中断,不会抛出InterruptedException

public void acquireUninterruptibly(int permits)

不可中断的获取permits个信号量。

相比于acquire方法,acquireUninterruptibly方法不响应中断,不会抛出InterruptedException。实际上内部调用AQS的acquireShared方法,这实际上就是共享式获取资源的模版方法式。

/**
 * 获取一个信号量,没有则一直阻塞,直到在其他线程提供信号量并唤醒该线程。
 * 获取一个信号量就立即返回,将可用的信号量数减 1。
 */
public void acquireUninterruptibly() {
    //内部调用AQS的acquireShared方法
    //这实际上就是共享式不可中断获取资源模版方法
    sync.acquireShared(1);
}

/**
 * AQS的acquireShared方法
 * 共享式不可中断获取资源模版方法
 *
 * @param arg 获取的资源数量
 */
public final void acquireShared(int arg) {
    //并没有检查中断
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

/**
 * 获取permits个信号量,没有则一直阻塞,直到在其他线程提供信号量并唤醒该线程。
 *
 * @param permits 获取的信号量数量
 */
public void acquireUninterruptibly(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    //参数就是permits
    sync.acquireShared(permits);
}

2.4 超时可中断获取信号量

public boolean tryAcquire(long timeout, TimeUnit unit)

超时可中断的获取一个信号量,没有则一直阻塞,直到在其他线程提供信号量并唤醒该线程或者线程被中断或者阻塞超时。获取一个信号量就立即返回,将可用的信号量数减 1。

如果调用此方法时已被中断或者等待时被中断,则抛出 InterruptedException,并且清除当前线程的已中断状态。

public boolean tryAcquire(int permits,long timeout,TimeUnit unit)

超时可中断的获取permits 个信号量。

实际上内部调用AQS的tryAcquireSharedNanos方法,这实际上就是共享式超时可中断获取资源的模版方法。

/**
 * @param timeout 超时时间
 * @param unit    时间单位
 * @return 是否获取资源成功
 * @throws InterruptedException 如果调用此方法时已被中断或者等待时被中断
 */
public boolean tryAcquire(long timeout, TimeUnit unit)
        throws InterruptedException {
    //实际上就是调用的AQS的共享式超时获取资源的方法,获取1个资源
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

/**
 * @param permits 获取的资源数量
 * @param timeout 超时时间
 * @param unit    时间单位
 * @return 是否获取资源成功
 * @throws InterruptedException 如果调用此方法时已被中断或者等待时被中断
 */
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
        throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    //实际上就是调用的AQS的共享式超时获取资源的方法,获取permits个资源
    return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}

/**
 * AQS的共享式超时获取资源的模版方法,支持中断
 *
 * @param arg          参数
 * @param nanosTimeout 超时时间,纳秒
 * @return 是否获取资源成功
 * @throws InterruptedException 如果调用此方法时已被中断或者等待时被中断
 */
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    //最开始就检查一次,如果当前线程是被中断状态,直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //下面是一个||运算进行短路连接的代码,同样左边是调用子类实现的tryAcquireShared尝试获取资源,获取到了直接返回true
    //获取不到资源就执行doAcquireSharedNanos方法,这个方法是AQS的方法,因此超时机制是AQS帮我们实现的!
    return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
}

2.5 尝试获取信号量

public boolean tryAcquire()

仅在调用时至少存在至少一个可用信号量,才尝试获取一个信号量。

public boolean tryAcquire(int permits)

仅在调用时至少存在permits个的信号量,才尝试获取permits个信号量。

实际上内部就是直接调用的nonfairTryAcquireShared方法,即公平模式和非公平模式的tryAcquire实现是一样的!并且该方法不会阻塞线程,获取成功返回true,获取失败返回false!

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

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

2.6 释放信号量

public void release()

释放一个信号量,信号量总数加1。释放成功后,将唤醒在同步队列中等待获取信号量的结点(线程)!

public void release(int permits)

释放permits个信号量,信号量总数加permits。释放成功后,将唤醒在同步队列中等待获取信号量的结点(线程)!

公平模式和非公平模式的信号量的释放都是一样的。实际上内部调用AQS的releaseShared方法,这实际上就是共享式释放资源的模版方法。

/**
 * 释放一个信号量,信号量总数加1。
 */
public void release() {
    //内部调用AQS的releaseShared方法
    //这实际上就是共享式释放资源的模版方法
    sync.releaseShared(1);
}

/**
 * 释放permits个信号量,信号量总数加permits。
 *
 * @param permits 释放的信号量个数
 */
public void release(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    //参数就是permits
    sync.releaseShared(permits);
}

/**
 * AQS的共享模式下释放资源的模版方法。
 * 如果成功释放则会调用doReleaseShared
 */
public final boolean releaseShared(int arg) {
    //tryReleaseShared释放信号量资源,该方法由子类自己实现
    if (tryReleaseShared(arg)) {
        //释放成功,必定调用doReleaseShared尝试唤醒后继结点,即阻塞的线程
        doReleaseShared();
        return true;
    }
    return false;
}

/**
 * Sync的tryReleaseShared实现
 *
 * @param releases 要释放的资源数量
 * @return true 成功 false 失败
 */
protected final boolean tryReleaseShared(int releases) {
    for (; ; ) {
        //很简单,就是尝试CAS的增加state值,增加releases
        int current = getState();
        int next = current + releases;
        //这里可以知道,信号量资源数量不可超过int的最大值
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        //CAS的增加state值,CAS成功之后返回true,否则循环重试
        if (compareAndSetState(current, next))
            return true;
    }
}

3.Semaphore的使用

Semaphore可以用来控制多线程对于共享资源访问的并发量!

案例:若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用,每个工人之多工作10秒,最后统计工作量。

我们可以通过Semaphore与之前的CountDownLatch搭配线程池来轻松实现。我们能发现,采用非公平模式的Semaphore时工人的总工作量大部分情况下要高于采用公平模式的工人总工作量,即非公平模式的执行效率更高(这是不一定的)。我们还能发现,在非公平模式工人的总工作量高于公平模式的工人总工作量时,非公平模式下总会有某些工人工(特别是工人0、1、2)作量更多,而另一些工人工作量更少,这就是线程饥饿!

/**
 * @author lx
 */
public class SemaphoreTest {

    /**
     * 机器数目,实际上就是信号量为5,非公平模式
     */
    private static Semaphore semaphore = new Semaphore(5, false);
    /**
     * 机器数目,实际上就是信号量为5,公平模式
     */
    //private static Semaphore semaphore = new Semaphore(5, true);

    /**
     * 当所有工人都完成任务,那么统计工作量
     */
    private static CountDownLatch countDownLatch = new CountDownLatch(10);

    /**
     * 工人数目,8
     */
    private static final int NUM = 10;

    /**
     * 当前时间
     */
    private static final long NOW = System.nanoTime();

    /**
     * 纳秒单位
     */
    private static final long NANOUNIT = 1000000000;

    /**
     * 工作量
     */
    private static final LongAdder WORKLOAD = new LongAdder();

    static class Worker implements Runnable {
        public Worker(int num) {
            this.num = num;
        }

        private int num;
        private long timed = 20 * NANOUNIT;

        @Override
        public void run() {
            while (true) {
                //获取信号量
                try {
                    if (semaphore.tryAcquire(timed, TimeUnit.NANOSECONDS)) {
                        System.out.println("工人" + this.num + "占用一个机器在生产...");
                        //占用一定时间
                        LockSupport.parkNanos((long) (NANOUNIT * num * 0.5));
                        //统一调整为2秒,将会看到更明显的Semaphore效果
                        //LockSupport.parkNanos((long) (NANOUNIT * 2));

                        System.out.println("工人" + this.num + "生产完毕,释放出机器");
                        //释放信号量
                        //每个工人最多执行20秒
                        WORKLOAD.increment();
                        if ((timed = timed - (System.nanoTime() - NOW)) <= 0) {
                            semaphore.release();
                            countDownLatch.countDown();
                            break;
                        }
                        semaphore.release();
                    } else {
                        countDownLatch.countDown();
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < NUM; i++) {
            executorService.execute(new Worker(i));
        }
        executorService.shutdown();
        countDownLatch.await();
        System.out.println("工作完毕,空闲机器为:" + semaphore.availablePermits());
        System.out.println("总工作量为:" + WORKLOAD.sum());
    }
}

4.Semaphore的总结

Semaphore和CountDownLatch的原理都差不多,都是直接使用AQS的共享模式实现自己的逻辑,都是对于AQS的state资源的利用,但是它们却实现了不同的功能,CountDownLatch中state被看作一个倒计数器,当state变为0时,表示线程可以放开执行。而Semaphore中的state被看作信号量资源,获取不到资源则可能会阻塞,获取到资源则可以访问共享区域,共享区域使用完毕要记得还回信号量。

很明显Semaphore的信号量资源很像锁资源,但是我们前面就说过他们的不同,那就是锁资源是和获得锁的线程绑定的,而这里的信号量资源并没有和线程绑定,也就是说你可以让一些线程不停的“释放信号量”,而另一些线程只是不停的“获取信号量”,这在AQS内部实际上就是对state状态的值的改变而已,与线程无关!

通常Semaphore可以用来控制多线程对于共享资源访问的并发量,在上面的案例中我们就见过!另外还需要注意的是,如果在AQS的同步队列中队头结点线程需要获取n个资源,目前有m个资源,如果m小于n,那么这个队列中的头结点线程以及后面的所有结点线程都会因为不能获取到资源而继续阻塞,即使头结点后面的结点中的线程所需的资源数量小于m也不行。即已经在AQS同步队列中阻塞的线程,只能按照先进先出的顺序去获取资源,如果头部线程因为所需资源数量不够而一直阻塞,那么队列后面的线程必定不能获取资源!

和CountDownLatch一样,Semaphore的源码看起来非常简单,那是因为复杂的线程等待、唤醒机制都被AQS实现了,如果想要真正了解Semaphore的原理,那么AQS是必须要了解的。实际上如果学会了AQS,那么JUC中的锁或者其他同步组件就很简单了!

以上就是详解Java信号量Semaphore的原理及使用的详细内容,更多关于Java信号量Semaphore的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java中Semaphore(信号量)的使用方法

    Semaphore的作用: 在java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了解决多个线程并发同一资源造成的数据不一致的问题.在另外一种场景下,一个资源有多个副本可供同时使用,比如打印机房有多个打印机.厕所有多个坑可供同时使用,这种情况下,Java提供了另外的并发访问控制--资源的多副本的并发访问控制,今天学习的信号量Semaphore即是其中的一种. Semaphore实现

  • Java 信号量Semaphore的实现

    近日于LeetCode看题遇1114 按序打印,获悉一解法使用了Semaphore,顺势研究,记心得于此. 此解视Semaphore为锁,以保证同一时刻单线程的顺序执行.在此原题上,我作出如下更改. package test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class

  • 详解Java 信号量Semaphore

    Semaphore也是一个同步器,和前面两篇说的CountDownLatch和CyclicBarrier不同,这是递增的,初始化的时候可以指定一个值,但是不需要知道需要同步的线程个数,只需要在同步的地方调用acquire方法时指定需要同步的线程个数: 一.简单使用 同步两个子线程,只有其中两个子线程执行完毕,主线程才会执行: package com.example.demo.study; import java.util.concurrent.ExecutorService; import ja

  • Java并发编程之Semaphore(信号量)详解及实例

    Java并发编程之Semaphore(信号量)详解及实例 概述 通常情况下,可能有多个线程同时访问数目很少的资源,如客户端建立了若干个线程同时访问同一数据库,这势必会造成服务端资源被耗尽的地步,那么怎样能够有效的来控制不可预知的接入量呢?及在同一时刻只能获得指定数目的数据库连接,在JDK1.5 java.util.concurrent 包中引入了Semaphore(信号量),信号量是在简单上锁的基础上实现的,相当于能令线程安全执行,并初始化为可用资源个数的计数器,通常用于限制可以访问某些资源(物

  • Java信号量Semaphore原理及代码实例

    Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目.自从5.0开始,jdk在java.util.concurrent包里提供了Semaphore 的官方实现,因此大家不需要自己去实现Semaphore. 下面的类使用信号量控制对内容池的访问: import java.util.concurrent.Semaphore; class Pool { private static final int MAX_AVAILABLE = 100; private final Sema

  • 详解Java信号量Semaphore的原理及使用

    目录 1.Semaphore的概述 2.Semaphore的原理 2.1 基本结构 2.2 可中断获取信号量 2.3 不可中断获取信号量 2.4 超时可中断获取信号量 2.5 尝试获取信号量 2.6 释放信号量 3.Semaphore的使用 4.Semaphore的总结 1.Semaphore的概述 public class Semaphore extends Object implements Serializable Semaphore来自于JDK1.5的JUC包,直译过来就是信号量,被作为

  • 详解Java Synchronized的实现原理

    目录 Synchronized Synchronized的使用方式 Synchronized的底层实现 1.Java对象头 2.Monitor 3.线程状态流转在Monitor上体现 Synchronized 的锁升级 谈到多线程就不得不谈到Synchronized,重要性不言而喻,今天主要谈谈Synchronized的实现原理. Synchronized synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized 翻译为中文的意思是同步,也称之为”同步锁“.

  • 详解Java ReentrantReadWriteLock读写锁的原理与实现

    目录 概述 原理概述 加锁原理 图解过程 源码解析 解锁原理 图解过程 源码解析 概述 ReentrantReadWriteLock读写锁是使用AQS的集大成者,用了独占模式和共享模式.本文和大家一起理解下ReentrantReadWriteLock读写锁的实现原理.在这之前建议大家阅读下下面3篇关联文章: 深入浅出理解Java并发AQS的独占锁模式 深入浅出理解Java并发AQS的共享锁模式 通俗易懂读写锁ReentrantReadWriteLock的使用 原理概述 上图是ReentrantR

  • 详解Java线程池和Executor原理的分析

    详解Java线程池和Executor原理的分析 线程池作用与基本知识 在开始之前,我们先来讨论下"线程池"这个概念."线程池",顾名思义就是一个线程缓存.它是一个或者多个线程的集合,用户可以把需要执行的任务简单地扔给线程池,而不用过多的纠结与执行的细节.那么线程池有哪些作用?或者说与直接用Thread相比,有什么优势?我简单总结了以下几点: 减小线程创建和销毁带来的消耗 对于Java Thread的实现,我在前面的一篇blog中进行了分析.Java Thread与内

  • 详解Java 中泛型的实现原理

    泛型是 Java 开发中常用的技术,了解泛型的几种形式和实现泛型的基本原理,有助于写出更优质的代码.本文总结了 Java 泛型的三种形式以及泛型实现原理. 泛型 泛型的本质是对类型进行参数化,在代码逻辑不关注具体的数据类型时使用.例如:实现一个通用的排序算法,此时关注的是算法本身,而非排序的对象的类型. 泛型方法 如下定义了一个泛型方法, 声明了一个类型变量,它可以应用于参数,返回值,和方法内的代码逻辑. class GenericMethod{ public <T> T[] sort(T[]

  • 详解 Java HashMap 实现原理

    HashMap 是 Java 中最常见数据结构之一,它能够在 O(1) 时间复杂度存储键值对和根据键值读取值操作.本文将分析其内部实现原理(基于 jdk1.8.0_231). 数据结构 HashMap 是基于哈希值的一种映射,所谓映射,即可以根据 key 获取到相应的 value.例如:数组是一种的映射,根据下标能够取到值.不过相对于数组,HashMap 占用的存储空间更小,复杂度却同样为 O(1). HashMap 内部定义了一排"桶",用一个叫 table 的 Node 数组表示:

  • 详解Java TCC分布式事务实现原理

    概述 之前网上看到很多写分布式事务的文章,不过大多都是将分布式事务各种技术方案简单介绍一下.很多朋友看了还是不知道分布式事务到底怎么回事,在项目里到底如何使用. 所以这篇文章,就用大白话+手工绘图,并结合一个电商系统的案例实践,来给大家讲清楚到底什么是 TCC 分布式事务. 业务场景介绍 咱们先来看看业务场景,假设你现在有一个电商系统,里面有一个支付订单的场景. 那对一个订单支付之后,我们需要做下面的步骤: 更改订单的状态为"已支付" 扣减商品库存 给会员增加积分 创建销售出库单通知仓

  • 详解Java的类加载机制及热部署的原理

    一.什么是类加载 类的加载指的是将类的.class文件的二进制数据读入到内存中,将其放在运行数据区的方法去,然后再堆区创建一个java.lang.Class对象,用来封装类在方法区的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区的数据结构,并且向Java程序员提供了访问方法区的数据结构的接口. 类加载器并不需要等到某个类被"首次主动使用"时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.cla

  • 详解Java线程池的使用及工作原理

    一.什么是线程池? 线程池是一种用于实现计算机程序并发执行的软件设计模式.线程池维护多个线程,等待由调度程序分配任务以并发执行,该模型提高了性能,并避免了由于为短期任务频繁创建和销毁线程而导致的执行延迟. 二.线程池要解决什么问题? 说到线程池就一定要从线程的生命周期讲起. 从图中可以了解无论任务执行多久,每个线程都要经历从生到死的状态.而使用线程池就是为了避免线程的重复创建,从而节省了线程的New至Runnable, Running至Terminated的时间:同时也会复用线程,最小化的节省系

随机推荐