Semaphore 学习笔记

113 阅读4分钟

Semaphore:信号标; 旗语。

Java中表示可以控制线程去访问资源,也可以反馈线程在指定时间内是否可以获得可执行权的信号,从而对这些超时未获取执行权利的线程做另行安排。

Semaphore 的简单使用

acquire() 和 release()

private static void SemaphoreDemo1() {
    final Semaphore semaphore = new Semaphore(2);//1
    IntStream.range(0,4).forEach(i ->{
        new Thread(() ->{
            System.out.println(Thread.currentThread().getName() + "开始");
            try {
                semaphore.acquire();//2
                System.out.println(Thread.currentThread().getName() + "获取许可证");
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException interruptedException){
                interruptedException.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "释放许可证");
                semaphore.release();//3
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        },"thread" + (i + 1)).start();
    });
}
  1. 初始化一个持有两个信号的Semaphore;

  2. 调用acquire()方法获取一个信号;

    • acquire()方法每次只能获取一个信号
  3. 调用release()方法释放一个信号

    • release()方法每次只能释放一个信号

上述代码中有四个线程,执行结果是每次只能有两个线程获取到信号,休眠三秒释放信号后其他线程才可以获取到释放的信号;执行结果如下:

thread1开始
thread3开始
thread4开始
thread2开始
thread3获取许可证
thread1获取许可证
休眠中...
休眠中...
thread1释放许可证
thread3释放许可证
thread3结束
thread1结束
thread2获取许可证
休眠中...
thread4获取许可证
休眠中...
thread4释放许可证
thread2释放许可证
thread2结束
thread4结束

Semaphore 会被interrupted中断

private static void SemaphoreDemo2() throws InterruptedException {
    final Semaphore semaphore = new Semaphore(1);//1
    Thread thread = new Thread(() ->{
        try {
            semaphore.acquire(2);//2
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    });
    thread.start();
    TimeUnit.MICROSECONDS.sleep(500);
    thread.interrupt();//3
}
  1. 初始化一个只有一个信号的Semaphore
  2. 调用acquire(2)获取两个,但是此时只有一个信号,所以线程thread会阻塞
  3. main方法调用thread的interrupt()会中断线程,出现异常
  4. semaphore#acquireUninterruptibly() 则不会出现中断异常,而是会一直阻塞

执行结果如下:

java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:998)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
	at java.util.concurrent.Semaphore.acquire(Semaphore.java:467)
	at main.Test.lambda$SemaphoreDemo2$2(Test.java:75)
	at java.lang.Thread.run(Thread.java:748)

drainPermits() 一次性获取所有许可证

private static void SemaphoreDemo4() {
    final Semaphore semaphore = new Semaphore(10);
    new Thread(() ->{
        System.out.println("availablePermits:" + semaphore.availablePermits());
        semaphore.drainPermits();//一次性获取所有许可证
        System.out.println("availablePermits:" + semaphore.availablePermits());
        try {
            TimeUnit.SECONDS.sleep(3);
        }catch (Exception e){
            e.printStackTrace();
        }
        semaphore.release(5);
        System.out.println(Thread.currentThread().getName() + "结束");
    },"thread").start();
}

业务案例:

由上述API得知,Semaphore可以控制工作线程的数量,我们可以联想到在微服务中网关限流等操作;业务模拟:

public class T extends Thread {

    Semaphore semaphore;
    public T (Semaphore semaphore,String name){
        super(name);
        this.semaphore=semaphore;
    }

    @Override
    public void run() {
        try {
            //尝试在100毫秒内获取信号
            boolean b = semaphore.tryAcquire(100, TimeUnit.MILLISECONDS);
            if (b){
                System.out.println("线程:"+Thread.currentThread().getName()  +"获取到信号,执行任务");
                //休眠3秒,模拟执行任务
                TimeUnit.SECONDS.sleep(3);
                semaphore.release();
            } else {
                System.out.println("线程:"+Thread.currentThread().getName()+"获取信号失败");
                rollback();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 回滚程序,进行降级
     */
    private void rollback() {
        System.out.println("线程:"+Thread.currentThread().getName()+"未获取到信号,程序降级");
    }
}

模拟每次只能有四个线程执行任务:

Semaphore semaphore = new Semaphore(4);
for (int i = 0; i < 10; i++) {
    new Thread(new T(semaphore,"线程"+i)).start();
}

输出结果:

线程:Thread-5获取到信号,执行任务
线程:Thread-9获取到信号,执行任务
线程:Thread-4获取到信号,执行任务
线程:Thread-8获取到信号,执行任务
线程:Thread-1获取信号失败
线程:Thread-0获取信号失败
线程:Thread-7获取信号失败
线程:Thread-7未获取到信号,程序降级
线程:Thread-2获取信号失败
线程:Thread-2未获取到信号,程序降级
线程:Thread-3获取信号失败
线程:Thread-3未获取到信号,程序降级
线程:Thread-6获取信号失败
线程:Thread-6未获取到信号,程序降级
线程:Thread-0未获取到信号,程序降级
线程:Thread-1未获取到信号,程序降级


总结下Semaphore常用的方法:

方法描述
acquire()获取一个许可证,可以被打断,没有足够的许可证时阻塞等待
acquire(int permits)获取指定数量的许可证,可以被打断,没有足够的许可证时阻塞等待
acquireUninterruptibly()获取一个许可证,不可被打断,没有足够的许可证时阻塞等待
acquireUninterruptibly(int permits)获取指定数量的许可证,不可被打断,没有足够的许可证时阻塞等待
tryAcquire()尝试获取一个许可证,没有足够的许可证时程序继续执行,不会被阻塞
tryAcquire(int permits)尝试获取指定数量的许可证,没有足够的许可证时程序继续执行,不会被阻塞
tryAcquire(long timeout, TimeUnit unit)在指定的时间范围内尝试获取1个许可证,没有足够的许可证时程序继续执行, 不会被阻塞,在该时间方位内可以被打断
tryAcquire(int permits, long timeout, TimeUnit unit)在指定的时间范围内尝试获取指定数量的许可证,没有足够的许可证时程序 继续执行,不会被阻塞,在该时间方位内可以被打断
release()释放一个许可证
drainPermits()一次性获取所有可用的许可证
availablePermits()获取当前可用许可证数量的预估值
hasQueuedThreads()判断是否有处于等待获取许可证状态的线程
getQueueLength()获取处于等待获取许可证状态的线程的数量的预估值
getQueuedThreads()获取处于等待获取许可证状态的线程集合