1、Semaphore 是什么
Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。
可以把它简单的理解成我们停车场入口立着的那个显示屏,每有一辆车进入停车场显示屏就会显示剩余车位减1,每有一辆车从停车场出去,显示屏上显示的剩余车辆就会加1,当显示屏上的剩余车位为0时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止。
2、使用场景
朱勇用于那些资源有明确访问数量限制的场景,常用于限流 。
比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。
比如:停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。
acquire()
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
acquire(int permits)
获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
acquireUninterruptibly()
获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
tryAcquire()
尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
tryAcquire(long timeout, TimeUnit unit)
尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
hasQueuedThreads()
等待队列里是否还存在等待线程。
getQueueLength()
获取等待队列里阻塞的线程数。
drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。
availablePermits()
返回可用的令牌数量。
下面是几个例子
1、
类似于锁,但是锁只能有一个,而这个 信号灯可以有多个且其可以实现灯的释放可以是不同线程,就可以解决死锁的问题;如果构造器为1,那么就和ReentrantLock用法类似
package day04.part1;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 信号灯
*
* 类似于锁,但是锁只能有一个,而这个
* 信号灯可以有多个且其可以实现灯的释放可以是不同线程,
* 就可以解决死锁的问题;
* 如果构造器为1,那么就和ReentrantLock用法类似
* @author xzq
*/
public class SemaPhoreTest01 {
private static int a=10;
private static Semaphore sema=new Semaphore(1);
// private static Lock lock = new ReentrantLock();
public static void decrement(){
//lock.lock();
try {
sema.acquire(); //类似lock.lock()
a--;
System.out.println(Thread.currentThread().getName()+":a的值为:"+a);
Thread.sleep(1_000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
sema.release();
//lock.unlock();
}
}
public static void main(String[] args) {
new Thread("线程1"){
@Override
public void run() {
for(int i=1;i<=5;i++){
decrement();
}
}
}.start();
new Thread("线程2"){
@Override
public void run() {
for(int i=1;i<=5;i++){
decrement();
}
}
}.start();
}
}
2、
这个信号灯可以一次性拿多个和释放多个,这样就可以动态地去进行线程的锁的释放和持有
并且SemaPhore还提供有 获取当前灯的个数和当前等待线程数的API这样就可以在运行过程 中对其进行控制
package day04.part1;
import java.util.concurrent.Semaphore;
/**
* 信号灯
* 这个信号灯可以一次性拿多个和释放多个
* 这样就可以动态地去进行线程的锁的释放和持有
* 并且SemaPhore还提供有
* 获取当前灯的个数和当前等待线程数的API
* 这样就可以在运行过程中对其进行控制
* @author xzq
*/
public class SemaPhoreTest02 {
public static void main(String[] args) throws InterruptedException {
final Semaphore sema=new Semaphore(3);
new Thread("线程1:"){
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
sema.acquire(2);
System.out.println(threadName+"我要开始修车了,我拿走了2个扳手……");
//拿走2个扳手之后修车用时5秒
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(threadName+"车修好了,我放回去了1个扳手……");
sema.release();//拿走了2个 只还了一个 sema.release(2)
}
}
}.start();
new Thread("线程2:"){
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
Thread.sleep(2000);
sema.acquire();
System.out.println(threadName+"我要开始修车了,我拿走了1个扳手……");
//拿走1个扳手之后修车用时6秒
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(threadName+"车修好了,我放回去了1个扳手……");
sema.release();
}
}
}.start();
new Thread("线程3:"){
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
System.out.println(threadName+"我要开始修车了,我拿走了1个扳手……");
sema.acquire();
//拿走1个扳手之后修车用时3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(threadName+"车修好了,我放回去了1个扳手……");
sema.release();
}
}
}.start();
while(true){
System.out.println("主线程:当前的扳手还有"+sema.availablePermits()+"个");
Thread.sleep(1000);
System.out.println("主线程:当前估计有"+sema.getQueueLength()+"个线程排队等候拿扳手。");
}
}
}
3、对中断的感知
package day04.part1;
import java.util.concurrent.Semaphore;
/**
* 信号灯
* 对中断的感知
* @author xzq
*/
public class SemaPhoreTest03 {
public static void main(String[] args) throws InterruptedException {
final Semaphore sema=new Semaphore(2);
Thread t1 = new Thread("线程1:"){
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
System.out.println(threadName+"我要开始修车了,我准备拿走1个扳手……");
sema.acquire();
System.out.println(threadName+"我已经拿走了1个扳手……");
//拿走1个扳手之后修车用时8秒
Thread.sleep(8000);
System.out.println(threadName+"车修好了,我放回去了1个扳手……");
} catch (InterruptedException e) {
}finally {
sema.release();
}
}
};
t1.start();
Thread t2 = new Thread("线程2:"){
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
System.out.println(threadName+"我要开始修车了,我准备拿走1个扳手……");
sema.acquire();
System.out.println(threadName+"我已经拿走了1个扳手……");
//拿走1个扳手之后修车用时6秒
Thread.sleep(6000);
System.out.println(threadName+"车修好了,我放回去了1个扳手……");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
sema.release();
}
}
};
t2.start();
//为了让线程3是最后执行的这里休息一下
Thread.sleep(100);
Thread t3 = new Thread("线程3:"){
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName+"我要开始修车了,我准备拿走1个扳手……");
//使用acquireUninterruptibly方法就是隔绝对中断的感知
sema.acquireUninterruptibly();// 相当于 lock.lock();
try {
sema.acquire(); // 相当于 lock.lockInterruptibly();
System.out.println(threadName+"我已经拿走了1个扳手……");
try {
//拿走1个扳手之后修车用时5秒
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println(threadName+"由sleep抛出的中断异常!");
throw new InterruptedException();
}
System.out.println(threadName+"车修好了,我放回去了1个扳手……");
} catch (InterruptedException e) {
System.out.println(threadName+"此时还有"+sema.availablePermits()+"个扳手");
System.out.println(threadName+"我在通过SemaPhore等待的过程中被中断了!");
}finally {
sema.release();
}
}
};
t3.start();
/*
* 3秒后,线程1和线程2还没有把车修好
* 主线程就中断正在等待的线程3
*/
Thread.sleep(3000);
t3.interrupt();
System.out.println("主线程:已对线程3进行了中断。");
System.out.println("主线程执行结束!");
}
}
4、拿走全部的灯、尝试获取并设置超时时间
package day04.part1;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 信号灯
* 拿走全部的灯、尝试获取并设置超时时间
* @author xzq
*/
public class SemaPhoreTest04 {
public static void main(String[] args) throws InterruptedException {
final Semaphore sema=new Semaphore(10);
Thread t1 = new Thread("线程1:"){
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
System.out.println(threadName+"我要开始修车了,我准备拿走全部的扳手……");
int gainNum = sema.drainPermits();
System.out.println(threadName+"我已经拿走了全部的扳手……一共拿走了"+gainNum+"个");
//拿走1个扳手之后修车用时3秒 1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
}finally {
System.out.println(threadName+"车修好了,我放回去了1个扳手……");
sema.release();
}
}
};
t1.start();// 相当于 lock.lockInterruptibly();
/*Thread t2 = new Thread("线程2:"){
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
//让线程2,1秒后再去拿扳手
Thread.sleep(1000);
System.out.println(threadName+"我要开始修车了,我准备拿走1个扳手……");
sema.acquire();//这里等待线程1把扳手放回去 才能拿到扳手
System.out.println(threadName+"我已经拿走了1个扳手……");
//拿走1个扳手之后修车用时1秒
Thread.sleep(1000);
System.out.println(threadName+"车修好了,我放回去了1个扳手……");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
sema.release();
}
}
};
t2.start();*/
Thread t3 = new Thread("线程3:"){
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
/*Thread.sleep(2000);
System.out.println(threadName+"我要开始修车了,我准备拿走1个扳手……");
System.out.println(threadName+"tryAcquire之前有"+sema.availablePermits()+"个扳手");
boolean isAcquired = sema.tryAcquire();//这里尝试拿扳手,拿不到就直接往下执行,而不会在这里等待
System.out.println(threadName+"tryAcquire之后有"+sema.availablePermits()+"个扳手");*/
System.out.println(threadName+"我要开始修车了,我准备拿走2个扳手……");
System.out.println(threadName+"tryAcquire之前有"+sema.availablePermits()+"个扳手");
/*
尝试拿2个扳手,1秒钟后线程1放回去一个 但还是不够两个 3秒后超时 放弃等待
*/
boolean isAcquired = sema.tryAcquire(2, 3, TimeUnit.SECONDS);
System.out.println(threadName+"tryAcquire过了3秒之后有"+sema.availablePermits()+"个扳手");
if(!isAcquired){
System.out.println(threadName+"已经等了3秒了,还是没有2个扳手,那么我走了……");
System.out.println(threadName+"既然没有扳手那我不修车了,我要自己先干别的事,不在这里傻等!");
return;
}
// System.out.println(threadName+"我已经拿走了1个扳手……");
// System.out.println(threadName+"我已经拿走了2个扳手……");
//拿走1个扳手之后修车用时5秒
Thread.sleep(5000);
System.out.println(threadName+"车修好了,我放回去了1个扳手……");
} catch (InterruptedException e) {
}finally {
sema.release(1);
}
}
};
t3.start();
System.out.println("主线程执行结束!");
}
}
5、可以先放再拿
package day04.part1;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 信号灯
* 那么是否可以先放再拿呢?
* @author xzq
*/
public class SemaPhoreTest05 {
private static int a=10;
private static Lock myLock=new ReentrantLock();
private static Semaphore mySema=new Semaphore(1);
private static void decrementFive(){
try {
String threadName = Thread.currentThread().getName();
// boolean isMyLocked =false;
// isMyLocked= myLock.tryLock();
// System.out.println(threadName+"尝试加锁完毕,加锁成功了么:"+isMyLocked);
// myLock.lock();
mySema.tryAcquire();
for(int i=0;i<5;i++){
Thread.sleep(1_000);
a--;
System.out.println(threadName+"当前a的值是:"+a);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
mySema.release();
// myLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
// final Semaphore sema=new Semaphore(2);
// final Lock rLock=new ReentrantLock();
rLock.unlock();
rLock.lock();
sema.release(4); 可以先放再拿
System.out.println("当前有"+sema.availablePermits()+"个灯");
sema.acquire();
//
new Thread("子线程:"){
@Override
public void run() {
decrementFive();
}
}.start();
Thread.sleep(2000);
decrementFive();
System.out.println("主线程执行结束!");
}
}