semaphore是经常使用的Java多线程工具之一,中文意思是信号的意思。此类的主要做用是限制线程并发的数量,经过发放许可方式控制线程数量,能够说该类是synchronized关键字的升级版本,比synchronized功能更强大。java
经过semaphore实现一个线程的同步数据库
@Slf4j class ThreadA extends Thread{ private Service service; public ThreadA(Service service){ super(); this.service = service; } @Override public void run() { service.testMethod(); } } class ThreadB extends Thread{ private Service service; public ThreadB(Service service){ super(); this.service = service; } @Override public void run() { service.testMethod(); } } class ThreadC extends Thread{ private Service service; public ThreadC(Service service){ super(); this.service = service; } @Override public void run() { service.testMethod(); } } @Slf4j class Service{ private Semaphore semaphore = new Semaphore(1); public void testMethod(){ try { semaphore.acquire(); log.info("线程名称{},beginTime:{}",Thread.currentThread().getName(),System.currentTimeMillis()); Thread.sleep(5000); log.info("线程名称{},endTime:{}",Thread.currentThread().getName(),System.currentTimeMillis()); semaphore.release(); } catch (InterruptedException e) { log.error("线程错误{}",e.getMessage()); } } }
上面写了三个线程调用使用semaphore的服务,下面把线程启动一下:多线程
public static void main(String[] args) { Service service = new Service(); ThreadA a = new ThreadA(service); a.setName("A"); ThreadB b = new ThreadB(service); b.setName("B"); ThreadC c = new ThreadC(service); c.setName("C"); a.start(); b.start(); c.start(); }
我是经过main方法去启动的线程,若是用junit去启动的话,发现线程会启动不了(如今尚未搞懂是什么缘由)。并发
上面经过只用了一个许可,觉得这只容许一个线程执行acquire和release之间的代码,因此不管执行多少遍,结果都是下面的结果(相似于同步):ide
类Semaphore的构造参数permit设置是许可的个数,上面的例子是在一个许可下的状况,若是多个许可的话,那么acquire和release之间能够同时容许多个线程执行,其实构造参数中设置的许可只是初始化的许可,若是咱们作下面这个操做:函数
public static void main(String[] args) throws InterruptedException { Semaphore semaphore = new Semaphore(5); semaphore.acquire(); semaphore.acquire(); semaphore.acquire(); semaphore.acquire(); semaphore.acquire(); log.info("可用的许可有多少:{}",semaphore.availablePermits()); semaphore.release(); semaphore.release(); semaphore.release(); semaphore.release(); semaphore.release(); semaphore.release(); log.info("可用的许可有多少:{}",semaphore.availablePermits()); semaphore.release(4); log.info("可用的许可有多少:{}",semaphore.availablePermits()); }
看看运行结果:工具
发现可用的许可愈来愈多,可见经过release(int)是能够动态增长许可的。ui
acquireUninterruptibly:指的是获取的许可不容许被终端,在线程运行的时候,若是调用线程的interrupt,线程并不会抛出异常,并中止运行。this
availablePermits:能够获取当前可用的许可还有多少个。spa
drainPermits:可获取并返回当即可用的全部许可个数,而且将可用的许可置0。
getQueueLength:获取等待许可的线程个数。
hasQueuedThreads:判断当前有没有线程在等待这个许可。
刚刚在开始的那个案例中,咱们发现线程运行的顺序是A、B、C,其实线程的启动顺序是abc,形成的缘由是许可默认是公平的,即先启动的线程大几率先拿到许可,咱们能够经过在构造函数的第二个参数设置是否公平许可:
开启非公平许可后的执行结果是下面这样的:
tryAcquire:无阻塞的尝试获取许可,若是获取不到就返回false,程序继续往下走。
semaphore既然能够控制并发 的数量,这种功能能够用在pool技术中,好比一个字符串池,若干个线程能够同时访问池中的数据,可是同时只有其中的几个能够得到数据,使用完毕之后再放回池中,不少pool技术的实现都是这种思路吧,开始上代码,因此建立一个池:
class StrPool{ private int poolSize = 3; private Semaphore semaphore = new Semaphore(10); //许可数量设置成和池大小一致 private ReentrantLock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private List<String> pool = new ArrayList(); public StrPool() { for (int i = 0; i < poolSize; i++) { pool.add("str---" + (i + 1)); } } public String get(){ String str = null; try { semaphore.acquire(); //获取许可 lock.lock(); //同步锁 while (pool.isEmpty()) { System.out.println("------is waiting now ------- "); condition.await(); //若是池中没有字符串了,那就线程挂着,等有人放进新的字符串后 唤醒该线程 } str = pool.remove(0); lock.unlock(); } catch (InterruptedException e) { e.printStackTrace(); } return str == null ? "" : str; } public void put(String str){ lock.lock(); pool.add(str); condition.signalAll(); //若是有在等待的线程,进行唤醒 lock.unlock(); semaphore.release(); } }
特地把许可数设置成大于池的大小,为了观察是否是有的线程进入了等待状态,咱们再起写个线程去操做字符串池:
@Slf4j class PoolThread extends Thread{ private StrPool pool; public PoolThread(StrPool pool) { this.pool = pool; } @Override public void run() { String str = pool.get(); log.info("线程{},获取了池中的数据{}",Thread.currentThread().getName(),str); pool.put(str); } }
启动20个线程,看看运行结果:
public static void main(String[] args) throws InterruptedException { StrPool strPool = new StrPool(); PoolThread[] threads = new PoolThread[20]; for (int i = 0; i < 20; i++) { threads[i] = new PoolThread(strPool); } for (int i = 0; i < 20; i++) { threads[i].start(); } }
咱们看到一进来就有的线程就在的等待状态了。而后获取的结果就是池中的 1 到 3编号的字符串:
咱们经常使用的数据库链接池,其实能够这么实现,原理大同小异。