Semaphore基本使用及原理

Semaphore基本使用及原理

Semaphore基本使用及原理_第1张图片

文章目录

  • Semaphore基本使用及原理
    • 1 什么是Semaphore
    • 2 基本使用
    • 3 源码分析
      • 3.1 构造方法
      • 3.2 获取许可
        • 3.2.1 `tryAcquire`方法
        • 3.2.2 `acquire`方法
      • 3.3 释放许可
    • 4 总结

在前面的文章中我们学习了 AQSReentrantLockCountDownLatch的原理。在今天的文章中我们再学习一个 JUC下的工具类- Semaphore

今天的文章中我们会先介绍下Semaphore的使用,然后通过源码来学习下其是如何实现的。

1 什么是Semaphore

Semaphore(信号量),是JUC包下的一个工具类,我们可以通过其限制执行的线程数量,达到限流的效果。

当一个线程执行时先通过其方法进行获取许可操作,获取到许可的线程继续执行业务逻辑,当线程执行完成后进行释放许可操作,未获取达到许可的线程进行等待或者直接结束。

2 基本使用

Semaphore的使用也是比较简单的,我们创建一个Runnable的子类,如下:

private static class MyRunnable implements Runnable {
    // 成员属性 Semaphore对象
    private final Semaphore semaphore;

    public MyRunnable(Semaphore semaphore) {
        this.semaphore = semaphore;
    }

    public void run() {
        String threadName = Thread.currentThread().getName();
        // 获取许可
        boolean acquire = semaphore.tryAcquire();
        // 未获取到许可 结束
        if (!acquire) {
            System.out.println("线程【" + threadName + "】未获取到许可,结束");
            return;
        }
		// 获取到许可
        try {
            System.out.println("线程【" + threadName + "】获取到许可");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放许可
            semaphore.release();
            System.out.println("线程【" + threadName + "】释放许可");
        }
    }
}

测试方法如下:

public static void main(String[] args) {
    Semaphore semaphore = new Semaphore(2);
    for (int i = 0; i <= 10; i ++) {
        MyRunnable runnable = new MyRunnable(semaphore);
        Thread thread = new Thread(runnable, "Thread-" + i);
        thread.start();
    }
}

执行结果如下

Semaphore基本使用及原理_第2张图片

3 源码分析

Semaphore的类图如下图所示:

Semaphore基本使用及原理_第3张图片

SyncSemaphore的一个内部类,该类继承AQS,这个类又有公平和非公平的两个子类,这个内置的同步器实现Semaphore的功能。

3.1 构造方法

Semaphore中提供了两个构造方法,如下:

// 指定许可数量
public Semaphore(int permits) {
    // sync属性赋值   默认未非公平实现
    sync = new NonfairSync(permits);
}

// 指定许可数量和是否公平实现
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

内置同步器的构造方法如下:

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

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

Sync(int permits) {
    // 用state属性记录许可量
    setState(permits);
}

这些构造方法的逻辑是比较简单的哈,相信大家还记得在AQS中有一个state属性,当创建Semaphore时会将传递过来的许可量设置到同步器的state值,并将创建的同步器对象赋值给Semaphore中的sync属性。

3.2 获取许可

Semaphore中提供了如下的获取许可的方法

  • void acquire() throws InterruptedException 获取一个许可,会阻塞等待其他线程释放许可
  • void acquire(int permits) throws InterruptedException 获取指定的许可数 ,会阻塞等待其他线程释放
  • void acquireUninterruptibly() 获取一个许可 会阻塞等待其他线程释放许可 可被中断
  • void acquireUninterruptibly(int permits) 获取指定的许可数 会阻塞等待其他线程释放许可 可被中断
  • boolean tryAcquire() 尝试获取许可 不会进行阻塞等待
  • boolean tryAcquire(int permits) 尝试获取指定的许可数 不会阻塞等待
  • boolean tryAcquire(long timeout, TimeUnit unit) 尝试获取许可 可指定等待时间
  • boolean tryAcquire(int permits, long timeout, TimeUnit unit) 尝试获取指定的许可数 可指定等待时间

由于篇幅有限,我们这里不全部介绍了,我们只介绍下acquire()tryAcquire()两个方法,其他的方法实现大家自行查看吧,相差也不是很大。

3.2.1 tryAcquire方法

这个方法的返回值表示是否获取许可成功,不会阻塞等待其他线程释放许可,没有许可了会直返返回false,其源码如下:

public boolean tryAcquire() {
    // 调用Semaphore.Sync中的nonfairTryAcquireShared方法
    return sync.nonfairTryAcquireShared(1) >= 0;
}

final int nonfairTryAcquireShared(int acquires) {
    // 自旋
    for (;;) {
        // 获取剩余的许可量
        int available = getState();
        // 扣减需要的信号量后的值
        int remaining = available - acquires;
        // 信号量不足 或者CAS替换state失败  返回扣减后的信号量值
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

这个方法的逻辑比较简单,判断许可量是否充足,充足的话CAS修改state的值。判断分配所需数量后的值是否大于等于0。

3.2.2 acquire方法

这个方法没有返回值,当许可不足时会阻塞线程等待其他线程释放许可,其源码如下:

public void acquire() throws InterruptedException {
    // 调用AQS中的方法
    sync.acquireSharedInterruptibly(1);
}

// AQS中的方法
public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // tryAcquireShared是个模板方法,需要子类去实现
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

acquire的方法中,会调用到AQS中的acquireSharedInterruptibly方法,在这个方法中用到了模板方法模式,tryAcquireShared方法是一个模板方法,需要子类去实现。接下来我们分别看看在Semaphore中的公平和非公平模式都是如何实现的。

公平模式下的tryAcquireShared方法实现如下:

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;
    }
}

非公平模式下的tryAcquireShared方法实现如下:

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

final int nonfairTryAcquireShared(int acquires) {
    // 自旋
    for (;;) {
        // 获取剩余的许可量
        int available = getState();
        // 扣减需要的信号量后的值
        int remaining = available - acquires;
        // 信号量不足 或者CAS替换state失败  返回扣减后的信号量值
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

公平和非公平模式的实现的区别是在公平模式的实现中会先判断是否已经存在阻塞的线程了,存在的话不会再去竞争获取许可了。

AQS.doAcquireSharedInterruptibly方法的逻辑如下:

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 添加到AQS的阻塞队列中
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 获取节点的前置节点
            final Node p = node.predecessor();
            // 前置节点是头节点
            if (p == head) {
                // 尝试获取许可
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 清楚无用的节点并挂起当前线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            // 获取失败移除节点
            cancelAcquire(node);
    }
}

3.3 释放许可

释放许可的方法为release方法,其源码如下:

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

// AQS中的方法
public final boolean releaseShared(int arg) {
    // 模板方法  需要子类实现
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        // 成功
        return true;
    }
    // 失败
    return false;
}

Semaphore.Sync实现的tryReleaseShared方法逻辑如下:

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");
        // CAS修改state
        if (compareAndSetState(current, next))
            return true;
    }
}

AQS.doreleaseShared的逻辑如下:

private void doReleaseShared() {
    // 自旋
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒下个节点  LockSupport.unpark
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

4 总结

至此我们今天的文章就结束了,希望大家通过本文能够知道Semaphore的作用及如何使用,并能理解其实现的原理。

Semaphore是在AQS基础之上实现的一个工具,AQS的实现原理我们在前面的文章里已经介绍了,这里我们就没有详细的再去介绍。

Semaphore的剩余许可量是通过AQS中的state属性进行的记录,获取许可是将该值进行减少,释放许可是将该值进行增加,当没有足够的许可时,线程会加入到阻塞队列中等待其他线程释放许可并唤醒。

感谢您的阅读,如果感觉有帮助,欢迎关注公众号:“Bug搬运小能手”。

你可能感兴趣的:(并发,java)