Concurrent同步工具类03 - Semaphore
简介
Semaphore是一个计数信号量。信号量维护了一个许可集合; 通过acquire()和release() 来获取和释放访问许可证。只有通过acquire获取了许可证的线程才能执行,否则阻塞。 通过release释放许可证其他线程才能进行获取。
公平性:没有办法保证线程能够公平地从信号量中获得许可。也就是说,无法保证第一个调用 acquire() 的线程会是第一个获得一个许可的线程。如果第一个线程在等 待一个许可时发生阻塞,而第二个线程前来索要一个许可的时候刚好有一个许可被释 放出来,那么它就可能会在第一个线程之前获得许可。如果你想要强制公平, Semaphore 类有一个具有一个布尔类型的参数的构造函数,通过这个参数告知 Semaphore 是否要强制公平。强制公平会影响到并发性能,所以除非你确实需要它否 则不要启用它。
应用场景
Semaphore 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源,主要用来限流。
比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。
比如:停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。
重要方法
1 acquire() 2 获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。 3 4 acquire(int permits) 5 获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。 6 7 acquireUninterruptibly() 8 获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。 9 10 tryAcquire() 11 尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。 12 13 tryAcquire(long timeout, TimeUnit unit) 14 尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。 15 16 release() 17 释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。 18 19 hasQueuedThreads() 20 等待队列里是否还存在等待线程。 21 22 getQueueLength() 23 获取等待队列里阻塞的线程数。 24 25 drainPermits() 26 清空令牌把可用令牌数置为0,返回清空令牌的数量。 27 28 availablePermits() 29 返回可用的令牌数量。
应用举例:(Semaphore实现模拟数据库连接池)
1 package com.test.lesson01.db; 2 3 import java.util.concurrent.TimeUnit; 4 5 /** 6 * 连接对象 7 */ 8 public class Connect { 9 10 private static int count = 1; 11 private int id = count++; 12 public Connect(){ 13 14 //假设打开一个连接需要耗费1秒 15 try { 16 TimeUnit.MICROSECONDS.sleep(1000); 17 } catch (Exception e) { 18 e.printStackTrace(); 19 } 20 21 } 22 }
1 package com.test.lesson01.db; 2 3 import java.util.concurrent.Semaphore; 4 5 /** 6 * 连接池 7 */ 8 public class DataBasePool { 9 10 //连接池大小 11 private int size; 12 //当前数据库可用数 13 private int available; 14 //数据库连接集合 15 private Connect[] connects; 16 //连接状态 17 private boolean[] connectFlags; 18 //信号量对象 19 private Semaphore semaphore; 20 //初始化连接池大小 21 public DataBasePool(int size){ 22 23 this.size = size; 24 this.available = size; 25 //信号量初始值为10,每成功进行一次acquire()操作,信号数减1,release()操作,信号数加1 26 semaphore = new Semaphore(size,true); 27 connects = new Connect[size]; 28 connectFlags = new boolean[size]; 29 initConnects(); 30 } 31 //数据库初始化 32 private void initConnects() { 33 for (int i = 0; i < this.size; i++) { 34 connects[i] = new Connect(); 35 } 36 37 } 38 //获得某个数据库连接,采用semaphore来控制 39 public Connect openConnect() throws InterruptedException{ 40 //请求许可证,如何没有许可证,则一直阻塞 41 semaphore.acquire(); 42 return getConnect(); 43 } 44 //因为可能存在多个线程请求连接,所以应该设为synchronized 45 private synchronized Connect getConnect() { 46 47 for(int i=0;i<connectFlags.length;i++) 48 { 49 //找到没有被使用的连接 50 if(connectFlags[i]== false) 51 { 52 //将该连接设为被连接了 53 connectFlags[i] = true; 54 available--; 55 System.out.println("【"+Thread.currentThread().getName()+"】已获取连接------剩余连接数:" + available); 56 //获得这个连接 57 return connects[i]; 58 } 59 } 60 return null; 61 } 62 //释放连接 63 public synchronized void releaseConnect(Connect connect) 64 { 65 for (int i = 0; i < connectFlags.length; i++) { 66 if(connect == connects[i]) 67 { 68 connectFlags[i] = false; 69 available++; 70 System.out.println("【"+Thread.currentThread().getName()+"】已释放连接------剩余连接数:" + available); 71 //信号量-1 72 semaphore.release(); 73 } 74 } 75 } 76 77 78 79 }
1 package com.test.lesson01.db; 2 3 4 public class Test implements Runnable { 5 6 private static DataBasePool pool = new DataBasePool(10); 7 @Override 8 public void run() { 9 try { 10 //打开一个连接 11 Connect connect = pool.openConnect(); 12 Thread.sleep(100); 13 pool.releaseConnect(connect); 14 15 } catch (Exception e) { 16 e.printStackTrace(); 17 } 18 19 } 20 public static void main(String[] args) { 21 for (int i = 0; i < 15; i++) { 22 // 用来测试数据库连接的打开和关闭 23 Thread thread = new Thread(new Test()); 24 thread.start(); 25 } 26 } 27 28 }
运行结果为:(其中的一种)
1 【Thread-0】已获取连接------剩余连接数:9 2 【Thread-5】已获取连接------剩余连接数:8 3 【Thread-6】已获取连接------剩余连接数:7 4 【Thread-8】已获取连接------剩余连接数:6 5 【Thread-4】已获取连接------剩余连接数:5 6 【Thread-1】已获取连接------剩余连接数:4 7 【Thread-3】已获取连接------剩余连接数:3 8 【Thread-2】已获取连接------剩余连接数:2 9 【Thread-7】已获取连接------剩余连接数:1 10 【Thread-9】已获取连接------剩余连接数:0 11 【Thread-5】已释放连接------剩余连接数:1 12 【Thread-6】已释放连接------剩余连接数:2 13 【Thread-0】已释放连接------剩余连接数:3 14 【Thread-11】已获取连接------剩余连接数:2 15 【Thread-10】已获取连接------剩余连接数:1 16 【Thread-12】已获取连接------剩余连接数:0 17 【Thread-2】已释放连接------剩余连接数:1 18 【Thread-4】已释放连接------剩余连接数:2 19 【Thread-1】已释放连接------剩余连接数:3 20 【Thread-7】已释放连接------剩余连接数:4 21 【Thread-8】已释放连接------剩余连接数:5 22 【Thread-3】已释放连接------剩余连接数:6 23 【Thread-9】已释放连接------剩余连接数:7 24 【Thread-14】已获取连接------剩余连接数:6 25 【Thread-13】已获取连接------剩余连接数:5 26 【Thread-10】已释放连接------剩余连接数:6 27 【Thread-12】已释放连接------剩余连接数:7 28 【Thread-11】已释放连接------剩余连接数:8 29 【Thread-14】已释放连接------剩余连接数:9 30 【Thread-13】已释放连接------剩余连接数:10