1.Semaphore类介绍
Semaphore类是java\util\concurrent包下用于限制线程并发数量的操作类,如果不限制并发线程的数量,CPU资源将会很快耗尽,每个线程的执行时间将会非常缓慢,因为CPU要把时间片分给不同的线程对象,而且上下文切换也需要消耗时间,最终导致系统运行效率的低下,因此限制线程的并发数量非常有必要。
2.Semaphore类实现线程的同步
2.1 多线程中的同步概念:其实就是排着队执行一个任务,执行任务是一个一个执行,并不能并行执行,这样的优点有助于业务逻辑的正确性,不会出现线程安全的问题,保证软件系统功能上的正确性。
2.2 Semaphore类的初始化许可定为1,每次消费许可也是1,消费完之后没有可用许可,则其他线程需要等待,实现了线程的同步。
2.3示例代码
public class SemaphoreDemo {
//服务类
public class Service{
//初始化许可数量是1
Semaphore semaphore = new Semaphore(1);
public void serviceMethod(){
try {
//获取许可,1是获取许可数量,permits不输入默认是1
//初始化许可数量是1,本次消费完许可之后无可用许可,则其他线程需要继续等待许可释放,实现同步
semaphore.acquire(1);
System.out.println(Thread.currentThread().getName()+"获取了许可,时间"+System.currentTimeMillis());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"释放了许可,时间"+System.currentTimeMillis());
//释放许可
semaphore.release(1);
}
}
}
//线程类
public class MyThread extends Thread{
private Service service;
public MyThread(String name, Service service) {
super(name);
this.service = service;
}
@Override
public void run() {
//多线程请求serviceMethod
service.serviceMethod();
}
}
//业务代码
public static void main(String[] args) {
SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
Service service = semaphoreDemo.new Service();
MyThread[] myThreads = new MyThread[5];
for(int i = 0; i<5; i++){
MyThread myThread = semaphoreDemo.new MyThread("线程"+(i+1) , service);
myThreads[i] = myThread;
}
for(MyThread myThread : myThreads){
myThread.start();
}
}
}
3.Semaphore类构造方法
Semaphore(int permits) 方法,permits是允许线程获取的许可数。
Semaphore(int permits, boolean fair)方法,permits是允许线程获取的许可数,fair参数是设置是否为公平信号量,true表示公平信号量,线程启动顺序跟获取许可的顺序一致,该参数不传,默认是创建非公平信号量。
4.Semaphore类acquire(int permits)方法, release(int permits)方法及动态添加许可量
4.1 acquire(int permits)方法的作用是每调用一次acquire方法,就使用permits个许可。不传参默认使用1个许可。
4.2 release(int permits)方法的作用是没调用一次release方法,就释放release个许可。不传参默认释放1个许可。
4.3 availablePermits()方法是获取当前可用的permits许可数量(未被消费的许可数量)。
public static void main(String[] args) {
try {
Semaphore semaphore = new Semaphore(10);
semaphore.acquire(2);
System.out.println("消费2个许可,还剩可用许可个数:"+semaphore.availablePermits());
semaphore.acquire(2);
semaphore.acquire(2);
semaphore.acquire(2);
semaphore.acquire(2);
System.out.println("消费10个许可,还剩可用许可个数:"+semaphore.availablePermits());
semaphore.release(4);
System.out.println("释放4个许可,还剩可用许可个数:"+semaphore.availablePermits());
semaphore.release(4);
semaphore.release(4);
System.out.println("释放12个许可,还剩可用许可个数:"+semaphore.availablePermits());
} catch (Exception e) {
e.printStackTrace();
}
}
消费2个许可,还剩可用许可个数:8
消费10个许可,还剩可用许可个数:0
释放4个许可,还剩可用许可个数:4
释放12个许可,还剩可用许可个数:12
4.4 总结:初始化时定义了10个许可量,最终释放了12个个许可量,可用许可量变为了12,由此可见,new Semaphore(10)中10并不是最终的许可量,而是初始化的许可量,是可以动态变更的。
5.Semaphore类acquireUninterruptibly()方法
5.1 acquireUninterruptibly()方法是使等待获取许可的线程无法中断,不传参默认是获取1个许可
5.2 void acquireUninterruptibly(int permits)是其重载方法,permits是设置消费的许可数量,同样等待获取许可的过程中不允许被消费
5.3 acquire()等待获取许可的过程中可以被中断的示例:线程被中断
public class SemaphoreDemo {
//服务类
public class Service{
//初始化许可数量是1
Semaphore semaphore = new Semaphore(4,true);
public void serviceMethod(){
try {
//获取许可,1是获取许可数量,permits不输入默认是1
//初始化许可数量是1,本次消费完许可之后无可用许可,则其他线程需要继续等待许可释放,实现同步
semaphore.acquire(2);
System.out.println(Thread.currentThread().getName()+"获取了许可,时间"+System.currentTimeMillis());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"释放了许可,时间"+System.currentTimeMillis());
//释放许可
semaphore.release(2);
} catch (InterruptedException e) {
System.out.println("捕获到了"+Thread.currentThread().getName()+"的异常"+e.getMessage());
e.printStackTrace();
}
}
}
//线程类
public class MyThread extends Thread{
private Service service;
public MyThread(String name, Service service) {
super(name);
this.service = service;
}
@Override
public void run() {
//多线程请求serviceMethods
service.serviceMethod();
}
}
//业务代码
public static void main(String[] args) {
SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
Service service = semaphoreDemo.new Service();
MyThread[] myThreads = new MyThread[2];
for(int i = 0; i<2; i++){
MyThread myThread = semaphoreDemo.new MyThread("线程"+(i+1) , service);
myThreads[i] = myThread;
}
for(MyThread myThread : myThreads){
myThread.start();
}
myThreads[1].interrupt();
System.out.println(myThreads[1].getName()+"线程执行了中断");
}
}
线程1获取了许可,时间1594285214191
线程2线程执行了中断
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1302)
at java.util.concurrent.Semaphore.acquire(Semaphore.java:467)
at com.minxl.demo.multiThread.SemaphoreDemo$Service.serviceMethod(SemaphoreDemo.java:19)
at com.minxl.demo.multiThread.SemaphoreDemo$MyThread.run(SemaphoreDemo.java:43)
捕获到了线程2的异常null
线程1释放了许可,时间1594285217194
5.4 acquireUninterruptibly()等待获取许可的过程中不可以被中断的示例:
public class SemaphoreDemo {
//服务类
public class Service{
//初始化许可数量是1
Semaphore semaphore = new Semaphore(2,true);
public void serviceMethod(){
try {
//获取许可,1是获取许可数量,permits不输入默认是1
//初始化许可数量是1,本次消费完许可之后无可用许可,则其他线程需要继续等待许可释放,实现同步
semaphore.acquireUninterruptibly(2);
System.out.println(Thread.currentThread().getName()+"获取了许可,时间"+System.currentTimeMillis());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"释放了许可,时间"+System.currentTimeMillis());
//释放许可
semaphore.release(2);
} catch (InterruptedException e) {
System.out.println("捕获到了"+Thread.currentThread().getName()+"的异常"+e.getMessage());
e.printStackTrace();
}
}
}
//线程类
public class MyThread extends Thread{
private Service service;
public MyThread(String name, Service service) {
super(name);
this.service = service;
}
@Override
public void run() {
//多线程请求serviceMethods
service.serviceMethod();
}
}
//业务代码
public static void main(String[] args) {
SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
Service service = semaphoreDemo.new Service();
MyThread[] myThreads = new MyThread[2];
for(int i = 0; i<2; i++){
MyThread myThread = semaphoreDemo.new MyThread("线程"+(i+1) , service);
myThreads[i] = myThread;
}
for(MyThread myThread : myThreads){
myThread.start();
}
myThreads[1].interrupt();
System.out.println(myThreads[1].getName()+"线程执行了中断");
}
}
线程1获取了许可,时间1594285457752
线程2线程执行了中断
线程1释放了许可,时间1594285460755
线程2获取了许可,时间1594285460755
捕获到了线程2的异常sleep interrupted
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.minxl.demo.multiThread.SemaphoreDemo$Service.serviceMethod(SemaphoreDemo.java:20)
at com.minxl.demo.multiThread.SemaphoreDemo$MyThread.run(SemaphoreDemo.java:42)
上面代码可知,即使中断了线程,依然会继续等待获取许可,上面异常是因为sleep导致的,sleep模拟的是代码执行时间,不需要关注此异常。
6.Semaphore类availablePermits()方法与drainPermits方法
6.1 两个方法都是返回当前可用的许可数。
6.2 int availablePermits()返回Semaphore对象的当前可用的许可数,此方法经常用于调试。
6.3 int drainPermits()方法获取并立即返回当前可用的许可数,并将可用许可置为0.
7.Semaphore类getQueueLength()方法与hasQueuedThreads方法
7.1 int getQueueLength()方法是取得等待当前Semaphore对象许可的线程数。
7.2 boolean hasQueuedThreads()方法是判断有没有线程等待当前Semaphore对象的许可
8.Semaphore类tryAcquire()方法
8.1 无参tryAcquire()方法作用是尝试获取1个许可,如果获取不到返回false,此方法通常与if语句结合使用,其具有无阻塞特点。
无阻塞特点可以使线程不至于在同步地方一直等待下去,if语句不成立执行else,程序依然会继续执行。
public class SemaphoreDemo {
//服务类
public class Service{
//初始化许可数量是1
Semaphore semaphore = new Semaphore(1,true);
public void serviceMethod(){
try {
//tryAcquire方法尝试获取许可
if(semaphore.tryAcquire()){
System.out.println(Thread.currentThread().getName()+"获取了许可,时间"+System.currentTimeMillis());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"释放了许可,时间"+System.currentTimeMillis());
//释放许可
semaphore.release(2);
}else{
System.out.println(Thread.currentThread().getName()+"没有获取到许可,时间"+System.currentTimeMillis());
}
} catch (InterruptedException e) {
System.out.println("捕获到了"+Thread.currentThread().getName()+"的异常"+e.getMessage());
e.printStackTrace();
}
}
}
//线程类
public class MyThread extends Thread{
private Service service;
public MyThread(String name, Service service) {
super(name);
this.service = service;
}
@Override
public void run() {
//多线程请求serviceMethods
service.serviceMethod();
}
}
//业务代码
public static void main(String[] args) {
SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
Service service = semaphoreDemo.new Service();
MyThread[] myThreads = new MyThread[2];
for(int i = 0; i<2; i++){
MyThread myThread = semaphoreDemo.new MyThread("线程"+(i+1) , service);
myThreads[i] = myThread;
}
for(MyThread myThread : myThreads){
myThread.start();
}
}
}
线程2获取了许可,时间1594308915018
线程1没有获取到许可,时间1594308915018
线程2释放了许可,时间1594308918019
9.Semaphore类tryAcquire()的多个重载方法
9.1 boolean tryAcquire(int permits)方法是尝试获取permits个许可,获取不到返回false.
9.2 boolean tryAcquire(long timeout, TimeUnit unit)方法是在指定timeout时间内,尝试获取1个许可,获取不到返回false.
9.3 boolean tryAcquire(int permits, long timeout, TimeUnit unit)方法是在指定timeout时间内,尝试获取permits个许可,获取不到返回false.
10.Semaphore类实现多进路—多处理—多出路实验
也就是允许多个线程同时处理任务,各自处理各自任务。很简单使用acquire就可以实现
public class SemaphoreDemo {
//服务类
public class Service{
//初始化许可数量是1
Semaphore semaphore = new Semaphore(3);
public void serviceMethod(){
try {
//acquire获取许可
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"获取了许可----开始执行任务----时间"+System.currentTimeMillis());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"释放了许可----结束任务执行----时间"+System.currentTimeMillis());
//release释放许可
semaphore.release();
} catch (InterruptedException e) {
System.out.println("捕获到了"+Thread.currentThread().getName()+"的异常"+e.getMessage());
e.printStackTrace();
}
}
}
//线程类
public class MyThread extends Thread{
private Service service;
public MyThread(String name, Service service) {
super(name);
this.service = service;
}
@Override
public void run() {
//多线程请求serviceMethods
service.serviceMethod();
}
}
//业务代码
public static void main(String[] args) {
SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
Service service = semaphoreDemo.new Service();
MyThread[] myThreads = new MyThread[10];
for(int i = 0; i < myThreads.length; i++){
MyThread myThread = semaphoreDemo.new MyThread("线程"+(i+1) , service);
myThreads[i] = myThread;
}
for(MyThread myThread : myThreads){
myThread.start();
}
}
}
线程3获取了许可----开始执行任务----时间1594309898741
线程1获取了许可----开始执行任务----时间1594309898741
线程2获取了许可----开始执行任务----时间1594309898741
线程2释放了许可----结束任务执行----时间1594309901743
线程3释放了许可----结束任务执行----时间1594309901743
线程1释放了许可----结束任务执行----时间1594309901743
线程5获取了许可----开始执行任务----时间1594309901743
线程4获取了许可----开始执行任务----时间1594309901743
线程8获取了许可----开始执行任务----时间1594309901743
线程5释放了许可----结束任务执行----时间1594309904743
线程6获取了许可----开始执行任务----时间1594309904743
线程4释放了许可----结束任务执行----时间1594309904744
线程8释放了许可----结束任务执行----时间1594309904744
线程9获取了许可----开始执行任务----时间1594309904744
线程7获取了许可----开始执行任务----时间1594309904744
线程6释放了许可----结束任务执行----时间1594309907744
线程10获取了许可----开始执行任务----时间1594309907744
线程7释放了许可----结束任务执行----时间1594309907745
线程9释放了许可----结束任务执行----时间1594309907745
线程10释放了许可----结束任务执行----时间1594309910744
11.Semaphore类实现多进路—单处理—多出路实验
本实验允许多个线程同时处理任务,但是执行任务的顺序是同步的,也就是执行任务的代码堵塞,单处理。使用Semaphore和ReentrantLock 实现。
public class SemaphoreDemo {
//服务类
public class Service{
//初始化许可数量是3
Semaphore semaphore = new Semaphore(3);
//定义锁
ReentrantLock lock = new ReentrantLock();
public void serviceMethod(){
try {
//acquire获取许可,允许多个线程同时获取许可,多进路
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"获取了许可----时间"+System.currentTimeMillis());
//业务代码加锁,堵塞,单处理
lock.lock();
System.out.println(Thread.currentThread().getName()+"开始执行任务----时间"+System.currentTimeMillis());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"结束任务执行----时间"+System.currentTimeMillis());
//业务代码执行完毕,释放锁
lock.unlock();
System.out.println(Thread.currentThread().getName()+"释放了许可----时间"+System.currentTimeMillis());
//release释放许可,允许多个线程同时释放许可,多出路
semaphore.release();
} catch (InterruptedException e) {
System.out.println("捕获到了"+Thread.currentThread().getName()+"的异常"+e.getMessage());
e.printStackTrace();
}
}
}
//线程类
public class MyThread extends Thread{
private Service service;
public MyThread(String name, Service service) {
super(name);
this.service = service;
}
@Override
public void run() {
//多线程请求serviceMethods
service.serviceMethod();
}
}
//业务代码
public static void main(String[] args) {
SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
Service service = semaphoreDemo.new Service();
MyThread[] myThreads = new MyThread[6];
for(int i = 0; i < myThreads.length; i++){
MyThread myThread = semaphoreDemo.new MyThread("线程"+(i+1) , service);
myThreads[i] = myThread;
}
for(MyThread myThread : myThreads){
myThread.start();
}
}
}
线程3获取了许可----时间1594310311982
线程1获取了许可----时间1594310311982
线程4获取了许可----时间1594310311982
线程3开始执行任务----时间1594310311983
线程3结束任务执行----时间1594310314984
线程3释放了许可----时间1594310314984
线程1开始执行任务----时间1594310314984
线程2获取了许可----时间1594310314985
线程1结束任务执行----时间1594310317985
线程1释放了许可----时间1594310317985
线程4开始执行任务----时间1594310317985
线程5获取了许可----时间1594310317985
线程4结束任务执行----时间1594310320985
线程4释放了许可----时间1594310320985
线程2开始执行任务----时间1594310320985
线程6获取了许可----时间1594310320985
线程2结束任务执行----时间1594310323985
线程2释放了许可----时间1594310323985
线程5开始执行任务----时间1594310323985
线程5结束任务执行----时间1594310326986
线程5释放了许可----时间1594310326986
线程6开始执行任务----时间1594310326986
线程6结束任务执行----时间1594310329986
线程6释放了许可----时间1594310329986
12.使用Semaphore类创建字符串池
Semaphore类可以有效的对并发执行的线程数量进行控制,该功能可以应用在pool池技术中,可以设置同时访问pool池数据的线程数。
本实验的功能是同时有若干个线程访问池中的数据,但是同时只有一个线程可以取得池中的数据,使用完之后再放回池中。
public class PoolsBySemaphoreDemo {
//pool池中的存储最大数
private int poolMaxSize = 5;
//最大许可数
private Semaphore semaphore = new Semaphore(3);
//存储池中具体的字符串
LinkedList<String> list = new LinkedList<String>();
//锁
ReentrantLock lock = new ReentrantLock();
//condition
Condition condition = lock.newCondition();
//构造函数,初始化pool池
public PoolsBySemaphoreDemo() {
super();
for(int i = 0; i < poolMaxSize; i++){
list.addLast("字符串"+(i+1));
}
}
//pool池获取方法
public String get(){
String getString = null;
try {
//获取许可,才有机会操作下面业务代码
//此许可不需要释放,在归还方法释放,防止在池为空时候,不停的执行下面业务代码
semaphore.acquire();
lock.lock();
if(list.size() == 0){
//池中为空,等待生产,释放锁
condition.await();
}else{
//消费pool中数据
getString = list.removeLast();
}
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
return getString;
}
//pool池归还方法
public void put(String putString){
lock.lock();
list.addFirst(putString);
condition.signalAll();
lock.unlock();
//释放许可
semaphore.release();
}
//线程类
public static class MyThread extends Thread{
private PoolsBySemaphoreDemo Listpool;
public MyThread(String name, PoolsBySemaphoreDemo Listpool) {
super(name);
this.Listpool = Listpool;
}
@Override
public void run() {
//pool池取得数据
String value = Listpool.get();
System.out.println(Thread.currentThread().getName() + "线程取得pool中数据---" + value);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//pool池放回数据
Listpool.put(value);
}
}
//业务代码
public static void main(String[] args) {
PoolsBySemaphoreDemo listPool = new PoolsBySemaphoreDemo();
MyThread[] myThreads = new MyThread[20];
for(int i = 0; i < myThreads.length; i++){
MyThread myThread = new MyThread("线程"+(i+1) , listPool);
myThreads[i] = myThread;
}
for(MyThread myThread : myThreads){
myThread.start();
}
}
}
线程3线程取得pool中数据---字符串5
线程5线程取得pool中数据---字符串4
线程4线程取得pool中数据---字符串3
线程2线程取得pool中数据---字符串2
线程6线程取得pool中数据---字符串3
线程1线程取得pool中数据---字符串1
线程7线程取得pool中数据---字符串4
线程9线程取得pool中数据---字符串1
线程8线程取得pool中数据---字符串5
线程10线程取得pool中数据---字符串2
线程12线程取得pool中数据---字符串5
线程11线程取得pool中数据---字符串3
线程14线程取得pool中数据---字符串1
线程13线程取得pool中数据---字符串4
线程15线程取得pool中数据---字符串2
线程16线程取得pool中数据---字符串3
线程17线程取得pool中数据---字符串5
线程18线程取得pool中数据---字符串4
线程19线程取得pool中数据---字符串1
线程20线程取得pool中数据---字符串2
13.使用Semaphore类实现多生产者/多消费者模式
本实验实现了生产者,消费者模式,还限制了生产者消费者的数量。
public class RepastService {
//厨师数量
volatile private Semaphore setSemaphore = new Semaphore(5);
//就餐者数量
volatile private Semaphore getSemaphore = new Semaphore(10);
//锁
volatile private ReentrantLock lock = new ReentrantLock();
//生产者对象监视器
volatile private Condition setCondition = lock.newCondition();
//消费者对象监视器
volatile private Condition getCondition = lock.newCondition();
//餐具数,最多允许生成的数量
volatile private Object[] producePosition = new Object[3];
//是否全部消费完毕
private boolean isEmpty(){
boolean isEmpty = true;
for(Object object : producePosition){
if(object != null){
isEmpty = false;
break;
}
}
return isEmpty;
}
//是否生产完毕
private boolean isFull(){
boolean isFull = true;
for(Object object : producePosition){
if(object == null){
isFull = false;
break;
}
}
return isFull;
}
//生产方法
public void set(){
try {
//获取许可
setSemaphore.acquire();
lock.lock();
while (isFull()){
//暂时不需要生产
setCondition.await();
}
for(int i = 0; i < producePosition.length; i++){
if(producePosition[i] == null){
producePosition[i] = "数据" + (i+1);
System.out.println(Thread.currentThread().getName() + "生产了" + producePosition[i]);
break;
}
}
//唤醒消费者
getCondition.signalAll();
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放许可
setSemaphore.release();
}
}
//消费方法
public void get(){
try {
//获取许可
getSemaphore.acquire();
lock.lock();
while (isEmpty()){
//暂时没有可用产品
getCondition.await();
}
for(int i = 0; i < producePosition.length; i++){
if(producePosition[i] != null){
System.out.println(Thread.currentThread().getName() + "消费了" + producePosition[i]);
producePosition[i] = null;
break;
}
}
//唤醒生产者
setCondition.signalAll();
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放许可
getSemaphore.release();
}
}
//线程类
public static class SetThread extends Thread{
private RepastService repastService;
public SetThread(String name, RepastService repastService) {
super(name);
this.repastService = repastService;
}
@Override
public void run() {
//多线程请求serviceMethods
repastService.set();
}
}
//线程类
public static class GetThread extends Thread{
private RepastService repastService;
public GetThread(String name, RepastService repastService) {
super(name);
this.repastService = repastService;
}
@Override
public void run() {
//多线程请求serviceMethods
repastService.get();
}
}
//业务
public static void main(String[] args) {
RepastService repastService = new RepastService();
Thread[] arrayC = new Thread[20];
Thread[] arrayB = new Thread[20];
for (int i = 0; i < 20; i++) {
SetThread setThread = new SetThread("生产者线程" + (i + 1), repastService);
arrayC[i] = setThread;
GetThread getThread = new GetThread("消费者线程" + (i + 1), repastService);
arrayB[i] = getThread;
}
for (int i = 0; i < 20; i++) {
arrayC[i].start();
arrayB[i].start();
}
}
}
14.总结
Semaphore类提供了限制并发线程数的功能,它具有synchronized所不具有的强大功能,比如等待获取许可的时间可以加入等待时间,还可以尝试是否可以持有锁等拓展功能,可以说Semaphore类是强有力的控制并发线程个数的解决方案之一。