JDK1.5的并发包
java.util.concurrent
中提供了几个非常有用的工具类,这些工具类给我们在业务开发过程中提供了一种并发流程控制的手段,本文会基于实际应用场景介绍如何使用Semaphore,以及内部实现机制。
所谓Semaphore即 信号量 的意思,
这个叫法并不能更好的展示它的作用,更形象的说法应该是许可证管理器。
它可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。
Semaphore
内部维护了一组虚拟的许可,许可证的数量可以通过构造函数的参数指定。
acquire
方法获得许可证,如果许可数量为0,该线程则一直阻塞,直到有可用许可。release
释放许可证。Semaphore
和ReentrantLock
类似,获取许可证有公平策略和非公平许可策略,默认情况下使用非公平策略。
Semaphore的方法如下:
——Semaphore(permits)
初始化许可证数量的构造函数
——Semaphore(permits,fair)
初始化许可证数量和是否公平模式的构造函数
——isFair()
是否公平模式FIFO
——availablePermits()
获取当前可用的许可证数量
——acquire()
当前线程尝试去阻塞的获取1个许可证。
此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:
当前线程获取了1个可用的许可证,则会停止等待,继续执行。
当前线程被中断,则会抛出InterruptedException异常,并停止等待,继续执行。
——acquire(permits)
当前线程尝试去阻塞的获取permits个许可证。
此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:
当前线程获取了n个可用的许可证,则会停止等待,继续执行。
当前线程被中断,则会抛出InterruptedException异常,并停止等待,继续执行。
——acquireUninterruptibly()
当前线程尝试去阻塞的获取1个许可证(不可中断的)。
此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:
当前线程获取了1个可用的许可证,则会停止等待,继续执行。
——acquireUninterruptibly(permits)
当前线程尝试去阻塞的获取permits个许可证。
此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:
当前线程获取了n个可用的许可证,则会停止等待,继续执行。
——tryAcquire()
当前线程尝试去获取1个许可证。
此过程是非阻塞的,它只是在方法调用时进行一次尝试。
如果当前线程获取了1个可用的许可证,则会停止等待,继续执行,并返回true。
如果当前线程没有获得这个许可证,也会停止等待,继续执行,并返回false。
——tryAcquire(permits)
当前线程尝试去获取permits个许可证。
此过程是非阻塞的,它只是在方法调用时进行一次尝试。
如果当前线程获取了permits个可用的许可证,则会停止等待,继续执行,并返回true。
如果当前线程没有获得permits个许可证,也会停止等待,继续执行,并返回false。
——tryAcquire(timeout,TimeUnit)
当前线程在限定时间内,阻塞的尝试去获取1个许可证。
此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:
当前线程获取了可用的许可证,则会停止等待,继续执行,并返回true。
当前线程等待时间timeout超时,则会停止等待,继续执行,并返回false。
当前线程在timeout时间内被中断,则会抛出InterruptedException一次,并停止等待,继续执行。
——tryAcquire(permits,timeout,TimeUnit)
当前线程在限定时间内,阻塞的尝试去获取permits个许可证。
此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:
当前线程获取了可用的permits个许可证,则会停止等待,继续执行,并返回true。
当前线程等待时间timeout超时,则会停止等待,继续执行,并返回false。
当前线程在timeout时间内被中断,则会抛出InterruptedException一次,并停止等待,继续执行。
——release()
当前线程释放1个可用的许可证。
——release(permits)
当前线程释放permits个可用的许可证。
——drainPermits()
当前线程获得剩余的所有可用许可证。
——hasQueuedThreads()
判断当前Semaphore对象上是否存在正在等待许可证的线程。
——getQueueLength()
获取当前Semaphore对象上是正在等待许可证的线程数量。
Semaphore
可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。
假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore
做流量
控制(限制获取某种资源的线程数量)。
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* Semaphore 测试方法
* @description:
* @author: ArvinWoo
* @date: 2019年7月17日下午5:30:33
*/
public class SemaphoreTest {
//线程数量
private static final int COUNT = 40;
//信号量 最大许可证为10个
private static Semaphore semaphore = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < COUNT; i++) {
Executors.newFixedThreadPool(COUNT).execute(new SemaphoreTest.Task());
}
}
static class Task implements Runnable {
@Override
public void run() {
try {
// 读取文件操作( 当前线程尝试去阻塞的获取1个许可证。)
semaphore.acquire();
System.out.println("执行操作");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 存数据过程 (当前线程释放1个可用的许可证。)
semaphore.release();
}
}
}
}
package com.hsw.test.concurrent;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.RandomUtils;
/**
* Semaphore 测试方法
*
* @description:
* @author: ArvinWoo
* @date: 2019年7月17日下午5:30:33
*/
public class SemaphoreTest {
// 2个打饭窗口 公平队列
private static Semaphore semaphore = new Semaphore(2, true);
public static void main(String[] args) throws InterruptedException {
Thread[] class101 = new Thread[5];
for (int i = 0; i < 20; i++) {
// 前10个同学都在耐心的等待打饭
if (i < 10) {
new Thread(new Students("打饭学生" + i, semaphore, 0)).start();
} else if (i >= 10 && i < 15) {// 这5个学生没有耐心打饭,只会等1000毫秒
new Thread(new Students("泡面学生" + i, semaphore, 1)).start();
} else {
// 这5个学生没有耐心打饭
class101[i - 15] = new Thread(new Students("聚餐学生" + i, semaphore, 2));
class101[i - 15].start();
}
}
//
Thread.sleep(5000);
for (int i = 0; i < 5; i++) {
class101[i].interrupt();
}
}
static class Students implements Runnable {
// 学生姓名
private String name;
// 打饭许可证
private Semaphore semaphore;
// 打饭方式
// 0 一直等待直到打到饭
// 1 等了一会不耐烦了,回宿舍吃泡面了
// 2 打饭中途被其他同学叫走了,不再等待
private int type;
// 构造
public Students(String name, Semaphore semaphore, int type) {
this.name = name;
this.semaphore = semaphore;
this.type = type;
}
@Override
public void run() {
// 根据打饭情形分别进行不同的处理
switch (type) {
// 打饭时间
// 这个学生很有耐心,它会一直排队直到打到饭
case 0:
// 排队
semaphore.acquireUninterruptibly();
// 进行打饭
try {
Thread.sleep(RandomUtils.nextLong(1000, 3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将打饭机会让后后面的同学
semaphore.release();
// 打到了饭
System.out.println(name + " 终于打到了饭.");
break;
// 这个学生没有耐心,等了1000毫秒没打到饭,就回宿舍泡面了
case 1:
// 排队
try {
// 如果等待超时,则不再等待,回宿舍吃泡面
if (semaphore.tryAcquire(RandomUtils.nextInt(6000, 16000), TimeUnit.MILLISECONDS)) {
// 进行打饭
Thread.sleep(RandomUtils.nextLong(1000, 3000));
// 将打饭机会让后后面的同学
semaphore.release();
// 打到了饭
System.out.println(name + " 终于打到了饭.");
} else {
// 回宿舍吃泡面
System.out.println(name + " 回宿舍吃泡面.");
}
} catch (InterruptedException e) {
}
break;
// 这个学生也很有耐心,但是他们班突然宣布聚餐,它只能放弃打饭了
case 2:
// 排队
try {
semaphore.acquire();
// 进行打饭
Thread.sleep(RandomUtils.nextLong(1000, 3000));
// 将打饭机会让后后面的同学
semaphore.release();
// 打到了饭
System.out.println(name + " 终于打到了饭.");
} catch (InterruptedException e) {
System.out.println(name + " 全部聚餐,不再打饭.");
}
break;
default:
break;
}
}
}
}