Semaphore
Posted ashoftime
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Semaphore相关的知识,希望对你有一定的参考价值。
信号量,用于控制并发的线程的数目。信号量在JUC下的实现,每当一个线程进入临界区信号量减少,线程释放锁后信号量增加。
1.1 简单使用
初始化permit为10的信号量,acquire减少2,release增加2,本质上等价于permit=5,acquire release都是1的信号量,并发线程数目为5个。
public class Service { private Semaphore semaphore = new Semaphore(10); public void testMethod(){ try { semaphore.acquire(2); System.out.println(Thread.currentThread().getName()+" begain time"+System.currentTimeMillis()); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" end time"+System.currentTimeMillis()); semaphore.release(2); } }
Thread类把service对象注入。
public class Thread extends java.lang.Thread { private Service service; public Thread(Service service) { this.service = service; } @Override public void run() { service.testMethod(); } }
测试类同时开启10个线程。
public class Run { public static void main(String[] args) { Service service = new Service(); for (int i = 0; i < 10; i++) { Thread thread = new Thread(service); thread.setName(i+""); thread.start(); } } }
从控制台的输出结果看到同时最多有5个线程同时在处理机上。
0 begain time1556026437118 1 begain time1556026437118 3 begain time1556026437119 2 begain time1556026437119 4 begain time1556026437119 0 end time1556026442120 2 end time1556026442120 5 begain time1556026442120 3 end time1556026442120 1 end time1556026442120 7 begain time1556026442121 6 begain time1556026442121 4 end time1556026442120 8 begain time1556026442121 9 begain time1556026442121 5 end time1556026447124 9 end time1556026447124 7 end time1556026447124 6 end time1556026447124 8 end time1556026447124
1.2 release与permit
release每次增加permit的数目,可以大于初始化Semaphore时permit的数量。
public class testReleasePermit { public static void main(String[] args) throws InterruptedException { Semaphore semaphore = new Semaphore(4); System.out.println(semaphore.availablePermits()); semaphore.acquire(4); System.out.println(semaphore.availablePermits()); semaphore.release(1); semaphore.release(1); semaphore.release(1); semaphore.release(1); System.out.println(semaphore.availablePermits()); semaphore.release(4); System.out.println(semaphore.availablePermits()); } }
初始化4,acquire4次变为0,release4次变为4,继续release变为8。初始化permit只是该信号量在初始化的时候允许的permit数目,而非最大的数目。
4 0 4 8
1.3 信号量和中断
当线程无法获得信号量的时候,该线程会被被park起来,即线程进入了WAITING状态,在WAITING状态的线程在被中断的时候会抛出异常,所以一个线程在acquire失败变成WAITING状态的时候是可以被中断的
public class RunInter { public static void main(String[] args) throws InterruptedException { serviceInter serviceInter = new serviceInter(); myThread thread1 = new myThread(serviceInter); myThread thread2 = new myThread(serviceInter); thread1.start(); thread2.start(); Thread.sleep(10); System.out.println(thread2.getState()); thread2.interrupt(); } }
public class serviceInter { private Semaphore semaphore = new Semaphore(1); public void testMethod() { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+" begain"+System.currentTimeMillis()); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+" end"+System.currentTimeMillis()); semaphore.release(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+" 进入了中断"); e.printStackTrace(); } } }
Thread-0先获得了信号量并执行,Thread-1没有获得信号量进入WAITING状态,并且在main线程里调用thread2的interrupt方法后抛出了异常。
Thread-0 begain1556029651021 WAITING Thread-1 进入了中断 java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:998) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304) at java.util.concurrent.Semaphore.acquire(Semaphore.java:312) at Semaphore.serviceInter.testMethod(serviceInter.java:12) at Semaphore.myThread.run(myThread.java:14) Thread-0 end1556029655608
把acquire改成acquireUninterruptibly,禁止中断没有获得信号量的线程,同样的测试代码结果如下。虽然还是抛出了异常但是异常是在thread2获得了信号量之后抛出的。
Thread-0 begain1556030019636 WAITING Thread-0 end1556030020637 Thread-1 begain1556030020638 Thread-1 进入了中断 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at Semaphore.serviceNonInter.testMethod(serviceNonInter.java:12) at Semaphore.myThreadNoInter.run(myThreadNoInter.java:14)
1.4获取可用的信号量的个数
-
availablePermits,返回可用的信号量的个数
-
drainPermits,返回可用的信号量的个数并清零
public class getAvailable { public static void main(String[] args) throws InterruptedException { Semaphore semaphore = new Semaphore(10); semaphore.acquire(4); System.out.println(semaphore.availablePermits()); System.out.println(semaphore.drainPermits()); System.out.println(semaphore.availablePermits()); } }
1.5 获取在等待信号量线程的个数
- getQueueLength,获取在排队获取线程的个数
- hasQueueThreads,判断是否有线程在等待
getQueueLength并不是准确的,比如同时开启30个线程,在第一个线程获取信号量的时候剩下29个线程还没有完成启动过程,所以理应有29个线程在等待但最终输出的数字个能小于等于29。
比如我同时开启5个线程,在testMethod中打印getQueueLength,在第一个线程启动的时候并没有打印4而是2。
public static void main(String[] args) throws InterruptedException { serviceInter serviceInter = new serviceInter(); for (int i = 0; i < 5; i++) { myThread thread = new myThread(serviceInter); thread.start(); } }
Thread-0 begain1556030481874 2 Thread-0 end1556030482877 Thread-1 begain1556030482878 3 Thread-1 end1556030483883 Thread-2 begain1556030483883 2 Thread-2 end1556030484887 Thread-3 begain1556030484888 1 Thread-3 end1556030485888 Thread-4 begain1556030485888 0 Thread-4 end1556030486891
1.5 公平和非公平信号量
在信号量中,公平指的是获取信号量的顺序和启动线程的顺序相同,即先启动的线程能够先获取信号量,非公平则不提供这种保证,默认情况下是非公平的,因为这样效率更高。
理论上非公平信号量获取信号量的顺序和启动的顺序无关,但是我测试的结果却是按照顺序来的。
1.6 tryAcquire
一种非阻塞的实现,获取线程失败的时候不进入WAITING状态而是返回false,配合if判断可实现一个简单的CAS乐观锁。
以上是关于Semaphore的主要内容,如果未能解决你的问题,请参考以下文章