JDK并发包API-Semaphore使用
JDK并发包API-Semaphore使用
这是几年前写的旧文,此前发布Wordpress小站上,现在又重新整理。算是温故知新,后续会继续整理。如有错误望及时指出,在此感谢。
Semaphore应用场景
限制线程对资源的并发访问量,比如数据库连接,当访问量超过设定的大小时,线程的执行就会被阻塞或受限。
JDK版本
1.8
常用的API及说明
构造方法
默认为非公平实现,构造参数permits为int型,表示并发量。
acquire()
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
acquire(int permits)
获取一批令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
acquireUninterruptibly()
获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
tryAcquire()
尝试获得令牌,立即返回获取令牌成功或失败,不阻塞线程。
tryAcquire(long timeout, TimeUnit unit)
尝试获得令牌,在最多等待时间后立即返回获取令牌成功或失败,不阻塞线程。
release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
hasQueuedThreads()
等待队列里是否还存在等待线程。
getQueueLength()
获取等待队列里阻塞的线程数。
drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。
availablePermits()
返回可用的令牌数量。
代码实例
code_01
import java.io.IOException;
import java.util.concurrent.Semaphore;
public class SemaphoreTest1 {
//声明及实例化
static Semaphore semaphore = new Semaphore(1);
public static void main(String[] args) throws IOException {
//调用acquire发生并发访问时,观察各线程的状态
//这里为了结果显示的一致性,准备了三个线程t1,t2,t3,每个线程内部都调用acquire
//t1先执行,t2&t3后执行
//待三个线程都start以后,观察各线程的状态
//线程内部调用了sleep,但不影响我们最终的结果输出
Thread t1 = new Thread(new Worker(5000), "t1");
Thread t2 = new Thread(new Worker(100), "t2");
Thread t3 = new Thread(new Worker(100), "t3");
t1.start();
sleep(Thread.currentThread(), 100);
t2.start();
t3.start();
sleep(Thread.currentThread(), 5000);
System.out.println("--------------------");
System.out.println("p2 t1 thread,state:" + t1.getState());
System.out.println("p2 t2 thread,state:" + t2.getState());
System.out.println("p2 t3 thread,state:" + t3.getState());
}
public static void sleep(Thread t, long time) {
try {
t.sleep(time);
} catch (InterruptedException e) {
System.out.println("sleep Interrupted:" + t.getName());
e.printStackTrace();
}
}
static class Worker implements Runnable {
private long workTime;
public Worker(long workTime) {
this.workTime = workTime;
}
@Override
public void run() {
try {
System.out.println("thread:" + Thread.currentThread().getName() + ",start...");
semaphore.acquire();
System.out.println("thread:" + Thread.currentThread().getName() + ",acquire...");
if (workTime > 0) {
sleep(Thread.currentThread(), workTime);
}
System.out.println("thread:" + Thread.currentThread().getName() + ",workTime:" + workTime);
} catch (InterruptedException e) {
System.out.println("semaphore Interrupted:" + Thread.currentThread().getName());
e.printStackTrace();
}
}
}
}
在上面的code_01中,输出结果:
thread:t1,start...
thread:t1,acquire...
thread:t2,start...
thread:t3,start...
thread:t1,workTime:5000
--------------
p2 t1 thread,state:TERMINATED
p2 t2 thread,state:WAITING
p2 t3 thread,state:WAITING
我们发现在三个线程都start以后,t1先拿到了令牌,t2&t3进入阻塞等待状态,与预期结果相符。
但这段代码有另一个问题,t1执行结束以后,为什么t2&t3没有被唤醒呢?
这里引入了另一个api:release
在Semaphore中,我们必须手动的申请/释放所拥有的令牌,类似Object的wait/notify
所以code_01的代码其实是不正确的。
code_02
修改Worker的run方法为:
@Override
public void run() {
try {
System.out.println("thread:" + Thread.currentThread().getName() + ",start...");
semaphore.acquire();
System.out.println("thread:" + Thread.currentThread().getName() + ",acquire...");
if (workTime > 0) {
sleep(Thread.currentThread(), workTime);
}
System.out.println("thread:" + Thread.currentThread().getName() + ",workTime:" + workTime);
} catch (InterruptedException e) {
System.out.println("semaphore Interrupted:" + Thread.currentThread().getName());
e.printStackTrace();
}finally{
System.out.println("thread:" + Thread.currentThread().getName() + ",release...");
semaphore.release();
}
}
在finaly中手动的释放令牌,这样才能正确的唤醒其它线程。
code_03
import java.io.IOException;
import java.util.concurrent.Semaphore;
public class SemaphoreTest3 {
//声明及实例化
static Semaphore semaphore = new Semaphore(1);
public static void main(String[] args) throws IOException {
//调用tryAcquire发生并发访问时,观察各线程的状态
//这里为了结果显示的一致性,准备了三个线程t1,t2,t3,每个线程内部都调用acquire
//t1先执行,t2&t3后执行
Thread t1 = new Thread(new Worker(5000), "t1");
Thread t2 = new Thread(new Worker(100), "t2");
Thread t3 = new Thread(new Worker(100), "t3");
t1.start();
sleep(Thread.currentThread(), 100);
t2.start();
t3.start();
}
public static void sleep(Thread t, long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
System.out.println("sleep Interrupted:" + t.getName());
e.printStackTrace();
}
}
static class Worker implements Runnable {
private long workTime;
public Worker(long workTime) {
this.workTime = workTime;
}
@Override
public void run() {
try {
System.out.println("thread:" + Thread.currentThread().getName() + ",tryAcquire start...");
boolean flag = semaphore.tryAcquire();
System.out.println("thread:" + Thread.currentThread().getName() + ",tryAcquire flag:" + flag);
if (flag) {
System.out.println("thread:" + Thread.currentThread().getName() + ",获取到令牌.");
} else {
System.out.println("thread:" + Thread.currentThread().getName() + ",没有获取到令牌.");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("thread:" + Thread.currentThread().getName() + ",release...");
semaphore.release();
}
}
}
}
code_03输出
thread:t1,tryAcquire start...
thread:t1,tryAcquire flag:true
thread:t1,获取到令牌.
thread:t1,release...
thread:t2,tryAcquire start...
thread:t3,tryAcquire start...
thread:t3,tryAcquire flag:false
thread:t2,tryAcquire flag:true
thread:t2,获取到令牌.
thread:t2,release...
thread:t3,没有获取到令牌.
thread:t3,release...
疑问,在code_03演示tryAcquire方法,这个是我们一般调用比较多的方法,会立即返回获取的结果,方便后续的逻辑处理。
这里额外带来一个问题,如果没有获取到令牌,需不需要调用release方法呢?或者说,没有获取令牌的情况下,调用release方法会影响令牌的数量嘛?
带着这个疑问,准备代码 code_04 & code_05
code_04
单线程下的模拟
import java.io.IOException;
import java.util.concurrent.Semaphore;
public class SemaphoreTest4 {
static int initPermits = 2;
//声明及实例化
static Semaphore semaphore = new Semaphore(initPermits);
public static void main(String[] args) throws IOException, InterruptedException {
boolean flag = false;
System.out.println("p0 flag:" + flag + ",初始化容量为:" + initPermits + ",当前可用的令牌数量:" + semaphore.availablePermits() + ",队列里是否还有等待的线程:" + semaphore.hasQueuedThreads() + ",等待队列里阻塞的线程数:" + semaphore.getQueueLength());
flag = semaphore.tryAcquire();
System.out.println("p1 flag:" + flag + ",初始化容量为:" + initPermits + ",当前可用的令牌数量:" + semaphore.availablePermits() + ",队列里是否还有等待的线程:" + semaphore.hasQueuedThreads() + ",等待队列里阻塞的线程数:" + semaphore.getQueueLength());
flag = semaphore.tryAcquire();
System.out.println("p2 flag:" + flag + ",初始化容量为:" + initPermits + ",当前可用的令牌数量:" + semaphore.availablePermits() + ",队列里是否还有等待的线程:" + semaphore.hasQueuedThreads() + ",等待队列里阻塞的线程数:" + semaphore.getQueueLength());
flag = semaphore.tryAcquire();
System.out.println("p3 flag:" + flag + ",初始化容量为:" + initPermits + ",当前可用的令牌数量:" + semaphore.availablePermits() + ",队列里是否还有等待的线程:" + semaphore.hasQueuedThreads() + ",等待队列里阻塞的线程数:" + semaphore.getQueueLength());
semaphore.release();
System.out.println("p4 初始化容量为:" + initPermits + ",当前可用的令牌数量:" + semaphore.availablePermits() + ",队列里是否还有等待的线程:" + semaphore.hasQueuedThreads() + ",等待队列里阻塞的线程数:" + semaphore.getQueueLength());
semaphore.release();
System.out.println("p5 初始化容量为:" + initPermits + ",当前可用的令牌数量:" + semaphore.availablePermits() + ",队列里是否还有等待的线程:" + semaphore.hasQueuedThreads() + ",等待队列里阻塞的线程数:" + semaphore.getQueueLength());
semaphore.release();
System.out.println("p6 初始化容量为:" + initPermits + ",当前可用的令牌数量:" + semaphore.availablePermits() + ",队列里是否还有等待的线程:" + semaphore.hasQueuedThreads() + ",等待队列里阻塞的线程数:" + semaphore.getQueueLength());
semaphore.release();
System.out.println("p7 初始化容量为:" + initPermits + ",当前可用的令牌数量:" + semaphore.availablePermits() + ",队列里是否还有等待的线程:" + semaphore.hasQueuedThreads() + ",等待队列里阻塞的线程数:" + semaphore.getQueueLength());
}
}
输出结果:
p0 flag:false,初始化容量为:2,当前可用的令牌数量:2,队列里是否还有等待的线程:false,等待队列里阻塞的线程数:0
p1 flag:true,初始化容量为:2,当前可用的令牌数量:1,队列里是否还有等待的线程:false,等待队列里阻塞的线程数:0
p2 flag:true,初始化容量为:2,当前可用的令牌数量:0,队列里是否还有等待的线程:false,等待队列里阻塞的线程数:0
p3 flag:false,初始化容量为:2,当前可用的令牌数量:0,队列里是否还有等待的线程:false,等待队列里阻塞的线程数:0
p4 初始化容量为:2,当前可用的令牌数量:1,队列里是否还有等待的线程:false,等待队列里阻塞的线程数:0
p5 初始化容量为:2,当前可用的令牌数量:2,队列里是否还有等待的线程:false,等待队列里阻塞的线程数:0
p6 初始化容量为:2,当前可用的令牌数量:3,队列里是否还有等待的线程:false,等待队列里阻塞的线程数:0
p7 初始化容量为:2,当前可用的令牌数量:4,队列里是否还有等待的线程:false,等待队列里阻塞的线程数:0
code_05
多线程模拟
import java.io.IOException;
import java.util.concurrent.Semaphore;
public class SemaphoreTest5 {
static int initPermits = 1;
//声明及实例化
static Semaphore semaphore = new Semaphore(initPermits);
public static void main(String[] args) throws IOException, InterruptedException {
//演示两个线程,一个消费令牌,一个释放令牌,观察semaphore在两个线程之间,令牌有没有强制绑定关系,即t1线程申请的令牌,是不是只能t1线程释放?
new Thread(() -> {
try {
boolean flag = semaphore.tryAcquire();
if (flag) {
System.out.println("thread:" + Thread.currentThread().getName() + ",获取到令牌.");
} else {
System.out.println("thread:" + Thread.currentThread().getName() + ",没有获取到令牌.");
}
} finally {
//semaphore.release();
}
}, "t1").start();
new Thread(() -> {
semaphore.release();
System.out.println("thread:" + Thread.currentThread().getName() + ",release...");
}, "t2").start();
sleep(1000);
System.out.println("p0 初始化容量为:" + initPermits + ",当前可用的令牌数量:" + semaphore.availablePermits() + ",队列里是否还有等待的线程:" + semaphore.hasQueuedThreads() + ",等待队列里阻塞的线程数:" + semaphore.getQueueLength());
}
public static void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
System.out.println("sleep Interrupted:" + Thread.currentThread().getName());
e.printStackTrace();
}
}
}
输出结果:
thread:t1,获取到令牌.
thread:t2,release...
p0 初始化容量为:1,当前可用的令牌数量:1,队列里是否还有等待的线程:false,等待队列里阻塞的线程数:0
总结
- semaphore可以很好的帮我们在多线程情况下的并发资源访问;
- acquire方法在未获取令牌时,线程会进入阻塞状态;
- tryAcquire方法会立即返回结果,并不会让线程进入阻塞状态,这个方法用的比较多些;
- release方法可以理解为增加令牌的数量,同时也有可能超过定义的并发数量,所以调用release方法时要做好判断;
- 获取令牌方法acquire和tryAcquire,与释放令牌release方法并不是一一对应的关系,与线程也没有关系;
code_02 & code_03里的代码中,调用release就是错误的,直接释放会影响令牌的可用数量。