JUC-线程协作-1-Semaphore

Semaphore 信号量,也是一个线程协作的工具,可以用来限制或管理数量有限的资源的使用

用法

信号量的作用是维护一个 “许可证” 的计数,线程可以获取许可证,然后信号量剩余许可证数量减一,当信号量所拥有的许可证为0的时候,下一个想要获取许可证的线程就需要等待,直到有另外的线程释放了许可证

常用的几个方法如下

  • 构造函数: 和上文的 CountDownLatch 一样, Semaphore 在初始化的时候也需要指定许可证的数量,还有一个构造参数是配置公平策略,如果是 true ,当有了新的许可证的时候,会把它给等待时间最长的线程
  • acquire()/acquireUninterruptibly(): 这两个方法是用来获取许可证的,从名字上来看就知道,后面的是可以在获取的过程种被打断的
  • tryAcquire(timeout)/tryAcquire(): 这两个方法是尝试获取许可证,在规定时间内立即返回,不会阻塞
  • release(): 释放许可证

使用案例

用个具体的代码演示以下信号量的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Slf4j
public class SemaphoreTest {
static Semaphore semaphore = new Semaphore(3);

public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Task()).start();
}
}

@Slf4j
static class Task implements Runnable{

@Override
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("获取到了许可证,执行具体逻辑...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("完成任务,释放许可证...");
semaphore.release();
}
}
}

新建一个3个许可证的信号量,然后新建 100 个线程都去获取证书,执行后再释放掉,看一下控制台输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
11:03:17.556 [Thread-1] INFO com.learning.java.cooperation.SemaphoreTest$Task - 获取到了许可证,执行具体逻辑...
11:03:17.556 [Thread-0] INFO com.learning.java.cooperation.SemaphoreTest$Task - 获取到了许可证,执行具体逻辑...
11:03:17.556 [Thread-2] INFO com.learning.java.cooperation.SemaphoreTest$Task - 获取到了许可证,执行具体逻辑...
11:03:18.579 [Thread-0] INFO com.learning.java.cooperation.SemaphoreTest$Task - 完成任务,释放许可证...
11:03:18.579 [Thread-2] INFO com.learning.java.cooperation.SemaphoreTest$Task - 完成任务,释放许可证...
11:03:18.579 [Thread-1] INFO com.learning.java.cooperation.SemaphoreTest$Task - 完成任务,释放许可证...
11:03:18.579 [Thread-4] INFO com.learning.java.cooperation.SemaphoreTest$Task - 获取到了许可证,执行具体逻辑...
11:03:18.579 [Thread-5] INFO com.learning.java.cooperation.SemaphoreTest$Task - 获取到了许可证,执行具体逻辑...
11:03:18.579 [Thread-3] INFO com.learning.java.cooperation.SemaphoreTest$Task - 获取到了许可证,执行具体逻辑...
11:03:19.579 [Thread-3] INFO com.learning.java.cooperation.SemaphoreTest$Task - 完成任务,释放许可证...
11:03:19.579 [Thread-5] INFO com.learning.java.cooperation.SemaphoreTest$Task - 完成任务,释放许可证...
11:03:19.579 [Thread-4] INFO com.learning.java.cooperation.SemaphoreTest$Task - 完成任务,释放许可证...
11:03:19.579 [Thread-6] INFO com.learning.java.cooperation.SemaphoreTest$Task - 获取到了许可证,执行具体逻辑...
11:03:19.579 [Thread-7] INFO com.learning.java.cooperation.SemaphoreTest$Task - 获取到了许可证,执行具体逻辑...
11:03:19.579 [Thread-29] INFO com.learning.java.cooperation.SemaphoreTest$Task - 获取到了许可证,执行具体逻辑...
11:03:20.579 [Thread-6] INFO com.learning.java.cooperation.SemaphoreTest$Task - 完成任务,释放许可证...
11:03:20.579 [Thread-7] INFO com.learning.java.cooperation.SemaphoreTest$Task - 完成任务,释放许可证...
11:03:20.579 [Thread-29] INFO com.learning.java.cooperation.SemaphoreTest$Task - 完成任务,释放许可证...

可以看到每次执行的都是3个线程,释放之后才会有下一批线程获取到,这里上面构造函数中没有设置为公平的,所以这里获取到许可证的线程并不是按顺序的

注意点

  1. acquire()release() 方法都有一个重载的方法,可以传入一个 int 类型的参数,表示获取/释放几个许可证,但是并不强制获取几个就得释放几个,比如获取两个释放一个也是可以的,但最后许可证会越来越少,会导致程序卡死
  2. 初始化的时候一般设置为公平的,更加合理
  3. 并不是必须由获取许可证的线程释放许可证, 比如A线程获取了许可证,可以由B线程释放