JAVA并发之Semaphore(信号量)

前面几篇文章我们讲了可重入锁和读写锁(见文末链接),本篇文章主要讲下Java并发包下面另一个工具类Semaphore(信号量)的原理。

Semaphore特点

  1. 是一种共享锁(类似于读写锁中的读锁)
  2. 基于AQS实现(AQS相关内容可以参考文末链接)
  3. 允许一定数量的线程获得锁

Semaphore例子

JDK源码对Semaphore的介绍是可以用来限制一定数量的线程访问某个资源,下面我们通过一个例子来看下具体是什么意思。

public static void main(String[] args) {
        DateFormat df = new SimpleDateFormat("HH:mm:ss---");
        Semaphore semaphore = new Semaphore(3);
        
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println(df.format(new Date()) + Thread.currentThread().getName() + " got resource");
                    Thread.sleep(2000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }
        };
        
        for (int i = 0; i < 9; i++) {
            new Thread(runnable, "Thread" + i).start();
        }
}
复制代码

上例中,我们创建了一个限制是3的信号量,同时启动了9个线程去执行各自的任务(这里都休眠了2秒钟),从下面打印的结果我们可以看到,同时只有3个线程能够获得信号量并执行。

11:12:29---Thread1 got resource
11:12:29---Thread3 got resource
11:12:29---Thread0 got resource
11:12:31---Thread4 got resource
11:12:31---Thread2 got resource
11:12:31---Thread6 got resource
11:12:33---Thread5 got resource
11:12:33---Thread7 got resource
11:12:33---Thread8 got resource
复制代码

Semaphore原理

Semaphore内部包含了一个Sync对象继承了AQS,而Sync对象又有两个子类NonfaireSync和FairSync,因此Semaphore也支持公平锁和非公平锁两个功能。

同时,公平锁和非公平锁也体现到了Semaphore的构造函数上面:

//默认非公平锁
public Semaphore(int permits) {
        sync = new NonfairSync(permits);
}
//fair为true时表示公平锁
public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
复制代码

获取Semaphore锁的方法如下,其主要方法有两个:

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
}
复制代码

tryAcquireShared: 尝试去获取锁,获取成功则返回正数或0,获取失败返回负数。

这里公平锁和非公平锁有不同的实现逻辑:

公平锁:前线程检查如果AQS里面有线程在排队了,则当返回-1,进入AQS排队;否则和其他线程竞争获取锁,竞争成功则获取锁返回,竞争失败则进入AQS排队。

protected int tryAcquireShared(int acquires) {
            for (;;) {
                if (hasQueuedPredecessors())
                    return -1;
               // 通过state来做线程数量限制
               // state的值和构造函数里面的permits参数是一个值
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
}
复制代码

非公平锁:当前线程直接参与获取锁的竞争,竞争成功则获取锁返回,竞争失败则进入AQS排队(不考虑是否AQS已经有其他线程在排队)。

final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
}
复制代码


doAcquireSharedInterruptibly:
类似于读锁调用的doAcquireShared方法,如果当前线程获取到锁,则唤醒它在AQS的后继结点,否则进入AQS排队。

释放Semaphore锁比较简单,公平锁和非公平锁都是一样的逻辑:

将state数值加1,并且唤醒正在AQS排队的其他线程。

Demo代码位置


src/main/java/net/weichitech/juc/SemaphoreTest.java · 小西学编程/java-learning - Gitee.com

相关文章

JAVA并发之ReentrantLock原理解析

JAVA并发之ReentrantReadWriteLock原理解析(一)

JAVA并发之ReentrantReadWriteLock原理解析(二)

猜你喜欢

转载自juejin.im/post/7046617950576443406