1.使用背景
案例一:抢车位
自驾游的朋友一般都会遇到这样的烦恼:去景区游玩,停车比较麻烦。因为停车场中的车位数量是一定的。当车位满了以后,其他想要进入停车场停车的车辆只能等待。等到其他车辆出来之后,才可以进入。站在并发角度来分析的话:停车场有多个停车位(多个共享资源),每个车辆只能停在其中一个位置上(互斥使用的),停车场的停车位是固定的(并发线程数量的控制)。
案例二:海底捞吃火锅
去海底捞吃火锅的时候,海底捞场地就餐桌数量是固定的,假设有5桌。现在来了8个人,那么其他3个就需要在门口候餐区等待加号。当有其他桌吃完离开之后,进去一个。
2.概述
2.1 理论:一个计数信号量,在概念上,信号量维持一组许可证。如果有必要,每个acquire()都会阻塞,直到许可证可用;每个release()添加许可证,潜在释放阻塞的获取方,但是没有使用实际的许可证对象;semaphore实际上保存的是许可证的数量。
2.2 用途:信号量通常用于限制线程数,而不是访问某些资源。作用:一是用于线程对共享资源的互斥访问,也就是访问互不影响;二是用于限制活动线程的数量,最主要的还是做流量控制。
2.3 原理:Semaphore内部主要通过AQS(AbstractQueuedSynchronizer)实现线程的管理 。
线程在运行时首先获取许可 , 如果成功 , 许可数就减1 , 线程运行 ; 当线程运行结束就释放许可 , 许可数就加1 ; 如果许可数为0 , 则获取失败 , 线程位于AQS的等待队列中 , 它会被其它释放许可的线程唤醒
3.api
3.1.构造器
//初始化指定数量许可证的Semaphore
Semaphore(int permits) :
//初始化指定数量许可证的Semaphore , 并指定是否公平模式(即FIFO先进先出原则)
Semaphore(int permits, boolean fair)
3.2.相关方法
//当前Semaphore是否是公平模式
boolean isFail() :
//当前Semaphore可用的许可证数量
int availablePermits() :
//判断当前Semaphore中是否存在正在等待许可证的线程
boolean hasQueuedThreads() :
//获取当前Semaphore中正在等待许可证的线程数量
int getQueueLength() :
//尝试获取一个可用的许可证 , 如果无可用许可证 ,
//当前线程会阻塞 , 如果线程被中断则抛出InterruptedException , 并停止阻塞继续执行
void acquire() throws InterruptedException :
//尝试获取指定数量的可用许可证 , 如果无可用或数量不足 , 当前线程会阻塞 ,
//如果线程被中断则抛出InterruptedException , 并停止阻塞继续执行
void acquire(int permits) throws InterruptedException :
//尝试获取1次可用许可证 , 非阻塞的 , 仅在调用该方法时尝试一次 , 获取到就继续执行并返回true .
//获取不到也会停止等待继续执行 , 并返回false . 此外有重载方法tryAcquire(int permits)
boolean tryAcquire() :
//在限定时间内尝试获取1个可用许可证 , 如果获取到则继续执行返回true , 如果超时则继续执行并返回false,
//如果被中断则抛出一次异常并继续执行,此外有重载方法tryAcquire(int permits, long timeout, TimeUnit unit)
boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException :
//不可中断的尝试获取1个可用许可证 , 此外有重载方法 acquireUninterruptibly(int permits)
void acquireUninterruptibly() :
//获取剩余所有可用的许可证
int drainPermits() :
//释放1个可用的许可证 , 重载方法acquire(int permits)
void release() :
//返回此信号量中当前可用的许可证数。
intavailablePermits():
//返回正在等待获取许可证的线程数
•intgetQueueLength():。
//是否有线程正在等待获取许可证。
•booleanhasQueuedThreads():
//减少 reduction 个许可证,是个 protected
•void reducePermit(sint reduction):
//返回所有等待获取许可证的线程集合,是个 protected 方法。
•Collection getQueuedThreads():
4.应用场景
4.1.生产消费问题,这是大头,大到转账取钱问题,小到并发读写队列,只要有对共享数据的并发读写访问,都可以算
4.2.秒杀场景,红包、促销等等,其实这也勉强可以算生产消费问题
4.3.研发框架,目前各种web 服务 、rpc服务、schedule服务等等,都是多线程服务,JUC的工具是保证并发服务高可用高性能的基础
5.实现一个数据库连接池
问题:为什么需要使用两个信号量?
解:在我们只使用一个信号量的情况下,比如在获取连接时,对获取的许可证作控制,这样可以保证当前只有规定的线程数持有许可证去连接池中获取连接,但此时释放连接并未得到控制,所以可以用无数个线程拿着许可证去释放连接到连接池中,这样会导致数据库可用连接数一直递增,所以需要双向的控制,获取连接的许可证需要控制,同理释放的许可证也需要得到控制。比如此时有我们控制最多有5个线程去获取连接,当有5个同时获取许可证的线程在执行时,也需要保证只有5个线程可以拿着许可证去归还连接。
/**
*类说明:演示Semaphore用法,一个数据库连接池的实现
*/
public class DBPoolSemaphore {
private final static int POOL_SIZE = 10;
//两个指示器,分别表示池子还有可用连接和已用连接
private final Semaphore useful,useless;
//存放数据库连接的容器
private static LinkedList<Connection> pool = new LinkedList<Connection>();
//初始化池
static {
for (int i = 0; i < POOL_SIZE; i++) {
pool.addLast(SqlConnectImpl.fetchConnection());
}
}
public DBPoolSemaphore() {
this.useful = new Semaphore(10);
this.useless = new Semaphore(0);
}
/*归还连接*/
public void returnConnect(Connection connection) throws InterruptedException {
if(connection!=null) {
System.out.println("当前有"+useful.getQueueLength()+"个线程等待数据库连接!!"
+"可用连接数:"+useful.availablePermits());
useless.acquire();
synchronized (pool) {
pool.addLast(connection);
}
useful.release();
}
}
/*从池子拿连接*/
public Connection takeConnect() throws InterruptedException {
useful.acquire();
Connection connection;
synchronized (pool) {
connection = pool.removeFirst();
}
useless.release();
return connection;
}
}