大白话讲信号量

132 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第20天,点击查看活动详情

1.举个栗子

信号量(Semaphore),什么是信号量呢?举个例子,当我们驾驶汽车或者要过马路的时候,需要等待红绿灯给出一个信号之后,我们才能确定要不要过马路,比如我们此时获得的是一个绿灯的信号那么我们可以通过路口,但是斜对面路口的车没有获得绿灯的信号,那它就只能等待,直到我们过了马路不需要这个信号了,然后它那边的红绿灯变为了绿色,获得了可以通过马路的信号,通过马路。

再举个例子,比如我们去饭店吃饭,饭店只能容纳5个人就餐,但是来了10个人,门口有个服务员,只允许五个人先进去,其他五个人等着,然后捏,有俩人吃完了,走了,空出来俩座位,然后服务员知道有俩空位了,门口喊一声:“空两位”,然后顾客尝试获取一个可以进去饭店的号码桌,获取成功之后,就进去用餐,用餐完毕释放号码桌,如果又有就餐完毕,离开的客户,那么服务员又能请进去两位,这个饭店就像当于是共享资源,每隔顾客就好比是线程,服务员就像当于是信号量,发的号码牌就像当于是令牌。

2.信号量工作原理

好,现在说下计算机中的信号量,其实就是限制共享资源的访问,只允许一定数量的线程访问共享资源,其他的线程只能等待,直到有线程运行完毕,释放资源,等待的线程才能进来。一般包含四个关键的操作部分。

  • 初始化

  • 获取信号

  • 释放信号

  • 关闭

首先的话是初始化一个信号量,初始化的值要大于0,当有线程要访问资源的时候,线程先acquire尝试获取一个信号量的令牌,如果获取到了就将信号量的值减1,如果再有线程要访问资源,那就再将信号量的初始值减1,直到为0,当再有线程进来时候,查询到信号量为零,那么就将这个线程挂起,直到有获取到令牌的线程释放为止。释放的时候会将信号量的值加1,然后通知被挂起的线程尝试获取信号量。

01_信号量.png

现在我们先上手实践下Semaphore

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreExample {
    public static void main(String[] args) {
         Semaphore semaphore=new Semaphore(3);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.execute(new EatTask(semaphore));
        }
        executorService.shutdown();
    }

    static class EatTask implements Runnable {
        private Semaphore semaphore;

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

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName()+"获得一个令牌");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放一个令牌");
                semaphore.release();
            }
        }
    }
}

3. Semaphore的方法以及使用场景

3.1 适用场景

说到这里,大家可能已经知道了,这些应用在什么场景了,一般就是限制资源访问的场景,比如限流。

3.2 常用方法

Semaphore大概有6个经常使用的方法,

  • Semaphore(int permits, boolean fair)permits表示令牌数,fair表示公平性,也就是说令牌释放的临界点是否允许提前抢占到令牌。
  • acquire(int permits)获取permits指定数量的令牌,如果可用数量不足,那就阻塞当线程。
  • tryAcquire(int permits),尝试获取permits指定数量的令牌,如果可用数量不足,那就返回false,否则返回ture。
  • release(int permits),释放permits指定数量的令牌
  • drainPermits(),获取当前线程可用的剩余令牌数
  • hasQueuedThread(),判断当前Semaphore是否存在正在等待令牌的线程。