🍋🔥 🍑🚀支持博主:点赞👍、收藏?、留言💬 🐾
1.🍅Semaphore简介
1.1🍑Semaphore是什么
-
Semaphore也叫信号量,在JDK1.5被引入,位于java.util.concurrent包中,可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。 -
Semaphore可以看作是synchronized关键字的升级版本,能够控制线程并发的数量,这一点是synchronized关键字做不到的。 -
内部维护了一个计数器,可加可减,acquire()方法是做减法,release()方法是做加法,具体内容见第三章节。
1.2🍑Semaphore的作用
- 限制线程并发的数量,如果不限制线程的并发数量,CPU资源会很快被耗尽。
2.🍅Semaphore中的方法(我们在之后的章节中会详细讲解,读者可以先大致看一下)
该部分API我们在之后的章节中会详细讲解,读者可以先大致看一下,之后需要用到此处API的时候可以进行查阅。
Semaphore类的结构视图: 官方API方法描述具体如下:
-
void acquire() 从该信号量获取许可证,阻止直到可用,或线程为 interrupted 。 -
void acquire(int permits) 从该信号量获取给定数量的许可证,阻止直到所有可用,否则线程为 interrupted 。 -
void acquireUninterruptibly() 从这个信号灯获取许可证,阻止一个可用的。 -
void acquireUninterruptibly(int permits) 从该信号量获取给定数量的许可证,阻止直到所有可用。 -
int availablePermits() 返回此信号量中当前可用的许可数。 -
int drainPermits() 获取并返回所有可立即获得的许可证。 -
protected Collection getQueuedThreads() 返回一个包含可能正在等待获取的线程的集合。 -
int getQueueLength() 返回等待获取的线程数的估计。 -
boolean hasQueuedThreads() 查询任何线程是否等待获取。 -
boolean isFair() 如果此信号量的公平设置为真,则返回 true 。 -
protected void reducePermits(int reduction) 缩小可用许可证的数量。 -
void release() 释放许可证,将其返回到信号量。 -
void release(int permits) 释放给定数量的许可证,将其返回到信号量。 -
String toString() 返回一个标识此信号量的字符串及其状态。 -
boolean tryAcquire() 从这个信号量获得许可证,只有在调用时可以使用该许可证。 -
boolean tryAcquire(int permits) 从这个信号量获取给定数量的许可证,只有在调用时全部可用。 -
boolean tryAcquire(int permits, long timeout, TimeUnit unit) 从该信号量获取给定数量的许可证,如果在给定的等待时间内全部可用,并且当前线程尚未 interrupted 。 -
boolean tryAcquire(long timeout, TimeUnit unit) 如果在给定的等待时间内可用,并且当前线程尚未 到达 interrupted,则从该信号量获取许可。
3.🍅acquire()方法和release()方法
3.1🍑构造方法、acquire()方法和release()方法简介
🍋构造方法:
- new Semaphore(),构造函数的permits参数是许可的意思,代表同一时间内,最多允许多少个线程同时执行acquire()和release()之间的代码
🍋acquire()方法:
- 无参数的acquire()作用是使用1个permits(许可),是减法操作,即减去对应的permits(许可);
- 有参数的acquire(),可以指定减去多少个permits(许可)数量。
🍋release()方法:
- 无参数的release()方法会加上1个permits(许可),是加法操作,即加上对应的permits(许可);
- 有参数的release()方法,可以动态添加permits(许可),比如new Semaphore(2),之后我们可以通过release(2),把permits(许可)数量变成4,这个可以说明构造方法中中的2并不是最终的permits(许可)数量,而只是初始数量。
3.2🍑Semaphore构造器中控制线程并发量为一(某段时间内只能并发一个线程)
因为我们在构造器中控制了并发的线程数量为一,所以当我们创建三个线程,同时启动,他们之间不会轮流着去执行,只能按照先后顺序执行完了前面一个才能执行后面的线程,具体代码如下:
🍋SemaphoreDemo01类:
import java.util.concurrent.Semaphore;
public class SemaphoreDemo01 {
private Semaphore semaphore = new Semaphore(1);
public void testMethodDemo() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "开始时间:" + System.currentTimeMillis());
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + "结束时间:" + System.currentTimeMillis());
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
🍋ThreadDemo类如下:
public class ThreadDemo extends Thread {
private SemaphoreDemo01 semaphoreDemo01;
public ThreadDemo(SemaphoreDemo01 semaphoreDemo01) {
super();
this.semaphoreDemo01 = semaphoreDemo01;
}
@Override
public void run() {
semaphoreDemo01.testMethodDemo();
}
public static void main(String[] args) {
SemaphoreDemo01 semaphoreDemo01 = new SemaphoreDemo01();
ThreadDemo a = new ThreadDemo(semaphoreDemo01);
ThreadDemo b = new ThreadDemo(semaphoreDemo01);
ThreadDemo c = new ThreadDemo(semaphoreDemo01);
a.setName("A");
b.setName("B");
c.setName("C");
a.start();
b.start();
c.start();
}
}
🍋运行结果:(线程按照先后顺序执行):
A开始时间:1648477661788
A结束时间:1648477671799
B开始时间:1648477671799
B结束时间:1648477681809
C开始时间:1648477681809
C结束时间:1648477691810
Process finished with exit code 0
3.3🍑Semaphore构造器中控制线程并发量为二(只能并发两个线程,即两个线程交替执行)
🍋此时我们只需要把3.1中SemaphoreDemo01类中的new Semaphore(1);改成new Semaphore(2);。如下:
package com.ysw.concurrent.ConcurrentProgramming.semaphoreAndExchanger;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo01 {
private Semaphore semaphore = new Semaphore(2);
public void testMethodDemo() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "开始时间:" + System.currentTimeMillis());
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + "结束时间:" + System.currentTimeMillis());
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
🍋运行结果:
B开始时间:1648478040379
A开始时间:1648478040379
A结束时间:1648478050379
B结束时间:1648478050379
C开始时间:1648478050379
C结束时间:1648478060387
Process finished with exit code 0
🍋ps:当构造方法中传入的permits大于1时,该类并不能保证线程的安全性,因为此时还是可能会出现多个线程共同访问实例变量,导致出现脏数据的情况
3.4🍑release()方法动态添加permits(许可)数量
🍋release()方法可以动态的添加permits(许可)数量,构造方法中的permits(许可)并不是最终的permits(许可)数量,而只是初始数量。代码如下:
import java.util.concurrent.Semaphore;
public class ReleaseMethodDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(8);
try {
System.out.println(semaphore.availablePermits());
semaphore.acquire();
semaphore.acquire(2);
System.out.println(semaphore.availablePermits());
semaphore.release();
semaphore.release(2);
semaphore.release(3);
System.out.println(semaphore.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
🍋运行结果:
8
5
11
Process finished with exit code 0
3.5🍑小结
- Semaphore内部维护了一组虚拟的许可(permits),许可的数量可以通过构造函数的参数指定。
- 访问特定资源前,必须使用acquire方法获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可。
访问资源后,使用release释放许可。
4.🍅原理
4.1🍑Semaphore的两种策略(公平策略和非公平策略)
Semaphore有两种策略
在上面的3.1中我们提到过Semaphore的构造方法
- 如果是一个参数的构造方法new Semaphore(int permits),默认采用的是非公平策略,对应的NonfairSync类;
- 如果想使用公平策略,那就需要使用两个参数的构造方法new Semaphore(int permits,boolean fair),当第二个参数为true时,才会采用公平策略,对应的FairSync类。
具体类图如下:
简化后的结果如下:
由类图可以看出,Semaphore中有属性Sync,而Sync类是继承于AQS的,Sync只是对 AQS的一个简单包装,起到了一个修饰作用,我们可以认为Semaphore还是使用AQS实现的。
未完待续…
Author:YuShiwen 上次更新日期:2022.05.04
|