基于核心源码和个人思考——Semaphore(信号量)

前提

  • AQS是基础,需先看懂AQS。

Semaphore

  • 信号量主要是通过内部类Sync继承AQS类,运用AQS共享队列机制实现的。
    • 信号量通过AQS的state进行计数。
    • 信号量通过AQS底层共享机制实现,许可与AQS资源的概念基本一致。
  • 信号量通过派发许可限制并发的数量,默认是可中断的,也实现了不可中断的接口(这个恰好和AQS相反,AQS默认方法是不可中断的)
  • 信号量没有对许可总量进行控制的操作,即可以出现初始化5个许可,再通过release方法“创造”5个许可的情况。因此许可的数量需要使用者自行维护。

内部类Sync

继承了AQS,重写了尝试获取锁的方法。

Sync方法

  • int getPermits() 获取剩余有效许可数量:通过AQS的state记录许可数量
final int getPermits() {
    return getState();
}
复制代码
  • int nonfairTryAcquireShared(int acquires) 非公平获取许可
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {  // 自旋
        int available = getState(); // 获取当前许可数量
        int remaining = available - acquires;
        if (remaining < 0 || // 许可耗尽
            compareAndSetState(available, remaining))  // CAS 控制原子性
            return remaining;
    }
}
复制代码
  • 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;
}
复制代码
  • void reducePermits(int reductions) 减少许可数量:该方法不会阻塞
for (;;) {   // 自旋
    int current = getState();
    int next = current - reductions;
    if (next > current) // 避免减少的太多,负数溢出...
        throw new Error("Permit count underflow");
    if (compareAndSetState(current, next))
        return;
}
复制代码
  • int drainPermits() 获取剩余所有许可
final int drainPermits() {
    for (;;) {  // 自旋
        int current = getState();
        if (current == 0 || compareAndSetState(current, 0))
            return current;
    }
}
复制代码

内部类NonfairSync

NonfairSync方法

  • int tryAcquireShared(int acquires) 尝试非公平方式获取许可
    • 直接调用了Sync内部类的nonfairTryAcquireShared方法。

内部类FairSync

FairSync方法

  • int tryAcquireShared(int acquires) 尝试获取许可(严格的先到先获取)
for (;;) {
    if (hasQueuedPredecessors())  // 等待队列是否有其他线程排在当前线程前面
        return -1; // 如果有则不尝试获取许可
    int available = getState();
    int remaining = available - acquires;
    if (remaining < 0 ||
        compareAndSetState(available, remaining))
        return remaining;
}
复制代码

属性

  • Sync sync

方法

获取许可方法

  • void acquire() 申请1个许可
  • void acquireUninterruptibly() 申请不可中断许可
  • boolean tryAcquire() 尝试申请许可
  • boolean tryAcquire(long timeout, TimeUnit unit) 尝试指定时间内获取许可
  • void acquire(int permits) 申请指定数量
  • void acquireUninterruptibly(int permits)
  • boolean tryAcquire(int permits)
  • boolean tryAcquire(int permits, long timeout, TimeUnit unit)

归还许可方法

  • void release() 归还许可
  • void release(int permits) 归还指定数量许可

其他对许可的操作

  • int drainPermits() 获取当前仅剩的所有许可数量
  • void reducePermits(int reduction) 强制减少许可数量,acquire会阻塞,这个不会。
  • int availablePermits() 获取剩余可申请的许可数量
  • boolean isFair() 是否已公平的方式申请许可(先申请先得)

获取队列信息的方法

  • boolean hasQueuedThreads() 是否有线程在等待
  • int getQueueLength() 获取等待队列长度
  • Collection getQueuedThreads() 获取等待资源的线程集合

相关问题

1. 如果线程acquire了一个许可,却release了5个许可,是否程序是否会抛异常?

  • 信号量没有维护每个线程获取的许可数和归还的许可数,因此问题提到的操作并不会导致程序因此抛异常,但可能会导致许可变多了的情况。
  • 测试程序:
    • 开一个for循环创造10个线程,每次只有两个许可派发给线程,每个线程获取1个许可,线程执行完归还5个许可。
    • (对照)开一个for循环创造10个线程,每次只有两个许可派发给线程,每个线程获取1个许可,线程执行完归还1个许可。
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

public class SemaphoreTest implements Runnable{
    static SemaphoreTest test = new SemaphoreTest();
    static Semaphore semaphore = new Semaphore(2);
    static CountDownLatch latch; // 通过countDownLatch阻塞主线程到所有循环结束。
    Thread mainThread;

    @Override
    public void run() {
        try {
            String name = Thread.currentThread().getName();
            System.out.println("子线程开始:" + name);
            System.out.println("子线程申请许可:" + name);
            semaphore.acquire();
            Thread.sleep(500);
            System.out.println("子线程获得许可:" + name);
            semaphore.release(5); // -----------------测试结果:1004毫秒
            // semaphore.release();  // --------------测试结果:2504毫秒
            System.out.println("子线程释放许可:" + name);
            System.out.println("子线程结束:" + name);
            latch.countDown();  // 标识线程结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int num = 10;
        latch = new CountDownLatch(num);
        long begin = (new Date()).getTime();
        for (int i = 0; i < num; i++) {
            (new Thread(new SemaphoreTest())).start();
        }
        latch.await();  // 阻塞到for循环所有的线程执行完
        long end = (new Date()).getTime();
        System.out.println("耗时:" + (end - begin) + "毫秒");
    }
}
复制代码
  • 结论:线程获取了1个许可,归还了5个许可,后续的并发量的确增大了许多。

猜你喜欢

转载自juejin.im/post/5e4e5c3351882549564b5209