二十八. Semaphore的使用详解

前言

Semaphore的官方注释如下。

计数信号量。从概念上讲,信号量维护一组许可证(permits)。通常,每次调用Semaphore#acquire方法时如果已经没有许可证,则会阻塞线程,直到获取到许可证。每次调用Semaphore#release方法都会添加一个许可证,此时某个阻塞的线程就会拿到许可证并解除阻塞状态。

Semaphore在初始化时需要指定许可证数,Semaphore#acquire方法会尝试获取一定数量的许可证,若许可证数量不足,则当前线程进入阻塞状态。相应地,获取到了许可证的线程,在执行完毕后,需要调用Semaphore#release方法来归还许可证。有以下两点需要注意。

  1. 建议在finally代码块中调用Semaphore#release方法,确保许可证的归还;
  2. 没有拿到许可证的线程,也可以调用Semaphore#release方法。故需要在业务代码层面保证Semaphore的正确使用。

正文

一. api整理

Semaphore的常用api如下表所示。

api 说明
Semaphore(int permits) 构造函数。permits指定初始许可证的数量,此时Semaphore的工作方式是非公平锁的方式
Semaphore(int permits, boolean fair) 构造函数。permits指定初始许可证的数量,fair指定Semaphore是公平锁还是非公平锁的工作方式
void acquire() 获取许可证。默认获取1张许可证,获取失败则当前线程进入阻塞状态,且响应中断
void acquire(int permits) 获取许可证。permits指定需要获取的许可证数量,获取失败则当前线程进入阻塞状态,且响应中断
void release() 归还许可证。默认归还1张许可证
void release(int permits) 归还许可证。permits指定归还许可证的数量
boolean tryAcquire() 尝试获取许可证。获取成功返回true,获取失败返回false。就算Semaphore的工作方式是公平锁方式,但是该方法在被调用的那一刻,也是会以非公平锁的方式尝试去获取许可证
boolean tryAcquire(long timeout, TimeUnit unit) 尝试在一段时间内获取许可证。获取成功返回true,获取失败返回false。该方法会尝试获取许可证,如果没有许可证,则等待timeout的时间,等待期间响应中断,如果等待时间到,也没有获取到许可证,则返回false

有一点需要注意,就是无论Semaphore公平锁还是非公平锁的工作方式,tryAcquire()方法会在调用的那一刻,以非公平锁的方式(就是不管是否已经有其它线程正在等待获取许可证)去获取许可证。如果想要tryAcquire()方法以公平锁的方式去获取许可证,则方法如下。

  1. Semaphore设置为公平锁的工作方式;
  2. 调用tryAcquire(0, TimeUnit.SECONDS)方法。

二. 公平锁方式的Semaphore的简单使用

公平锁方式的Semaphore能够保证在没有许可证时,最先等待获取许可证的线程一定能够在有许可证时最先获取到许可证。示例如下所示。

public class SemaphoreTest {

    @Test
    public void 公平锁方式的简单使用() throws Exception {
        // 创建Semaphore对象,且指定公平锁的工作方式
        Semaphore semaphore = new Semaphore(1, true);

        // 创建一个简单任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " 执行了");
                } catch (InterruptedException e) {
                    // ignore
                } finally {
                    semaphore.release();
                }
            }
        };

        // 创建多个线程来执行同一个任务
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable, "线程" + i);
            thread.start();
            // 放弃时间片,确保上一线程已经拿到许可证或者已经进入阻塞状态
            for (int k = 0; k < 100; k++) {
                Thread.yield();
            }
        }

        // 主线程睡眠1秒,以便观察现象
        Thread.sleep(1000);
    }

}

上述示例演示了在某一刻,同步队列中有等待获取许可证的线程,以及有正在调用acquire()方法获取许可证的线程,此时一定是同步队列中等待获取许可证最久的线程能够获取到许可证。运行测试程序,结果如下。

在这里插入图片描述

三. 非公平锁方式的Semaphore的简单使用

非公平锁方式的Semaphore不能够保证在没有许可证时,最先等待获取许可证的线程一定能够在有许可证时最先获取到许可证。示例如下所示。

public class SemaphoreTest {

    @Test
    public void 非公平锁方式的简单使用() throws Exception {
        // 创建Semaphore对象,且指定非公平锁的工作方式
        Semaphore semaphore = new Semaphore(1, false);

        // 创建一个简单任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " 执行了");
                } catch (InterruptedException e) {
                    // ignore
                } finally {
                    semaphore.release();
                }
            }
        };

        // 创建多个线程来执行同一个任务
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable, "线程" + i);
            thread.start();
            // 放弃时间片,确保上一线程已经拿到许可证或者已经进入阻塞状态
            for (int k = 0; k < 100; k++) {
                Thread.yield();
            }
        }

        // 主线程睡眠1秒,以便观察现象
        Thread.sleep(1000);
    }

}

上述示例演示了在某一刻,同步队列中有等待获取许可证的线程,以及有正在调用acquire()方法获取许可证的线程,此时会出现调用acquire()方法的线程比同步队列中等待最久的线程先获取到许可证的情况。运行测试程序,结果如下。

在这里插入图片描述

总结

关于Semaphore的使用,总结如下。

  1. Semaphore有两种工作方式,第一种是公平锁的工作方式,第二种是非公平锁的工作方式;
  2. 初始化Semaphore时需要指定初始许可证的数量permits,这个数量可以为负数,但是如果初始许可证数量指定为负数,那么需要保证在acquire()方法调用前先调用release()方法来填充许可证数量;
  3. 调用Semaphore#acquire方法能够获取许可证,获取到许可证的线程会从Semaphore#acquire方法立即返回,从而可以继续往下执行,而获取失败的线程就会阻塞在Semaphore#acquire方法上,直到获取到许可证,或者线程被中断;
  4. 调用Semaphore#release方法能够归还许可证,此时如果有线程正在等待获取许可证,那么其中一个线程能够获取到许可证并从等待中返回;
  5. Semaphore#release方法调用时,并不要求线程已经获取到许可证;
  6. Semaphore还提供了tryAcquire()方法来让线程在获取许可证失败时不进入阻塞状态而是直接返回false

你可能感兴趣的:(JAVA并发编程,Semaphore,java,信号量,许可证)