Semaphore类所提供的功能完全是synchorized关键字的升级版,但是它提供的功能更加的强大与方便,==主要作用就是控制线程并发的数量==,但是这一点,是synchorized所做不到的
常见API:
单词Semaphore的中文含义就是信号、信号系统,此类的主要作用的就是限制线程并发的数量,如果不限制现场并发的数量,则CPU资源很快被耗尽,每个线程执行的任务是相当缓慢。因为CPU要把时间片分配给不同的线程对象,而且上下文切换也要耗时,最终造成系统运行效率大幅降低。
Smaphore类发放许可的计算方式是“减法”操作。
多线程中的同步概念其实就是排队执行任务,执行任务是一个一个执行,不能并行执行,优点是有助于程序逻辑的正确性,不会出现非线程安全的问题,保证软件功能的稳定性。
现在使用一个案例来看Semaphore是如何限制并发数的:
public class Service {
//permits为1代表同一时间内,最多允许一个线程同时执行acquire()和release()之间的代码
private Semaphore semaphore = new Semaphore(1);
public void testMethod(){
try {
//使用1个许可,是减法操作
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"begin timer="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+"end timer="+System.currentTimeMillis());
semaphore.release();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service){
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service){
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class ThreadC extends Thread {
private Service service;
public ThreadC(Service service){
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class Run {
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();
}
}
运行结果:
有参方法acquire(int permits)的功能是每调用1次该方法,就使用x个许可
代码示例如下:
public class Service {
private Semaphore semaphore = new Semaphore(10);
public void testMethod() {
try {
semaphore.acquire(2);
System.out.println("Test");
Thread.sleep(5000);
semaphore.release(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
如果多次调用Semaphore类的release()或者release(int)方法时,还可以动态增加permits的个数,例如:
public class Run {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5);
try {
semaphore.acquire();
semaphore.acquire();
semaphore.acquire();
semaphore.acquire();
semaphore.acquire();
System.out.println(semaphore.availablePermits());
semaphore.release();
semaphore.release();
semaphore.release();
semaphore.release();
semaphore.release();
semaphore.release();
System.out.println(semaphore.availablePermits());
semaphore.release(4);
System.out.println(semaphore.availablePermits());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
如结果所示:new Semaphore(5)中的5并不是最终的许可证数量,仅仅是初始的状态值。
方法acquireUninterruptibly()的作用是使等待进入acquire()方法的线程,不允许被中断。
中断案例:
public class Service {
private Semaphore semaphore = new Semaphore(1);
public void testMethod() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" begin timer="+System.currentTimeMillis());
for(int i=0;i<10000/50;i++) {
System.out.println(i);
Thread.sleep(1000);
}
System.out.println(Thread.currentThread().getName()+" end timer="+System.currentTimeMillis());
semaphore.release();
} catch (InterruptedException e) {
System.out.println("线程"+Thread.currentThread().getName()+"进入了catch");
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
a.interrupt();
System.out.println("main中断了a");
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
运行结果如图:
不可中断案例:
将Service中的acquire()修改成acquireUninterruptibly()方法,其他代码无需修改
public class Service {
private Semaphore semaphore = new Semaphore(1);
public void testMethod() {
try {
//此处修改为不可中断获取
semaphore.acquireUninterruptibly();
System.out.println(Thread.currentThread().getName()+" begin timer="+System.currentTimeMillis());
for(int i=0;i<10000/50;i++) {
System.out.println(i);
Thread.sleep(1000);
}
System.out.println(Thread.currentThread().getName()+" end timer="+System.currentTimeMillis());
semaphore.release();
} catch (InterruptedException e) {
System.out.println("线程"+Thread.currentThread().getName()+"进入了catch");
e.printStackTrace();
}
}
}
在此我们可以看到当使用acquireUninterruptibly和acquire的使用场景是不一样的,acquire是等待许可的过程中允许中断的,acquireUninterruptibly是等待许可的过程中不允许中断的,acquireUninterruptibly()以及acquireUninterruptibly(int permits)作用是一样的,如果成功获取锁,则取得指定的permits许可个数,具体使用哪一个取决于我们业务逻辑和场景。
availablePermits()返回Semaphore对象中当前可用的许可数,此方法通常用于开发调试,因为许可的数量是实时在变化,不是一个固定的值
drainPermits()可以获取并返回立即可用的所有许可个数,并且将可用许可置为0
案例如下:
public class MyService {
private Semaphore semaphore = new Semaphore(10);
public void testMethod() {
try {
semaphore.acquire();
System.out.println(semaphore.availablePermits());
System.out.println(semaphore.drainPermits()+" "+semaphore.availablePermits());
System.out.println(semaphore.drainPermits()+" "+semaphore.availablePermits());
System.out.println(semaphore.drainPermits()+" "+semaphore.availablePermits());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
semaphore.release();
}
}
}
public class Run {
public static void main(String[] args) {
MyService service = new MyService();
service.testMethod();
}
}
从结果我们可以发现:
drainPermits()会一次性消费所有的许可,因此当我们不想其他线程同时去消费许可,可以使用此方法来提前消费
方法getQueueLength()的作用是取得等待许可的线程个数
方法hasQueueThreads()的作用是判断有没有线程在等待许可
这两个方法通常都是在判断当前有没有等待许可的线程信息时使用
案例如下:
public class MyService {
private Semaphore semaphore = new Semaphore(1);
public void testMethod() {
try {
semaphore.acquire();
Thread.sleep(1000);
System.out.println("还有大约:"+semaphore.getQueueLength()+"个线程在等待");
System.out.println("是否有线程正在等待信号量呢?"+semaphore.hasQueuedThreads());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}
}
public class MyThread extends Thread{
private MyService service;
public MyThread(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class Run {
public static void main(String[] args) {
MyService service = new MyService();
MyThread firstThread = new MyThread(service);
firstThread.start();
MyThread[] threadArray = new MyThread[4];
for(int i=0;i<4;i++) {
threadArray[i] = new MyThread(service);
threadArray[i].start();
}
}
}
无参方法tryAcquire()的作用是尝试地获取1个许可,如果获取不到则返回false,此方法通常与if语句结合使用,其具有无阻塞的特点。无阻塞的特点可以使线程不至于在同步处一直持续等待的状态,如果if语句判断不成立则线程会继续走else语句,程序会继续运行。
有参方法tryAcquire(int permits)的作用是尝试地获取x个许可,如果获取不到则返回false。
有参方法tryAcquire(int long timeout,TimeUnit unit)的作用是在指定的时间内尝试获取1个许可,如果获取不到则返回false。
有参方法tryAcquire(long permits,long timeout,TimeUnit unit)的作用是在指定的时间内尝试地获取x个许可,如果获取不到则返回false。
案例如下:
public class Service {
private Semaphore semaphore = new Semaphore(3);
public void testMethod() {
try {
if (semaphore.tryAcquire(3, 3, TimeUnit.SECONDS)) {
System.out.println("ThreadName="+Thread.currentThread().getName()+"首选进入!");
for (int i = 0; i < Integer.MAX_VALUE; i++) {
}
semaphore.release();
}else {
System.out.println("ThreadName="+Thread.currentThread().getName()+"未成功进入!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}
所谓的公平信号量是获得锁的顺序与线程启动的顺序有关,但不代表100%地获得信号量,仅仅是在概率上能得到保证,而非公平信号量则与其无关。
案例如下:
非公平信号量:
public class MyService {
private boolean isFair = false;
private Semaphore semaphore = new Semaphore(1,isFair);
public void testMethod() {
try {
semaphore.acquire();
System.out.println("ThreadName="+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}
}
public class MyThread extends Thread{
private MyService myService;
public MyThread(MyService myService) {
super();
this.myService = myService;
}
@Override
public void run() {
System.out.println("ThreadName="+this.getName()+"启动了!");
myService.testMethod();
}
}
public class Run {
public static void main(String[] args) {
MyService service = new MyService();
MyThread firstThread = new MyThread(service);
firstThread.start();
MyThread[] threadArray = new MyThread[4];
for (int i = 0; i < 4; i++) {
threadArray[i] = new MyThread(service);
threadArray[i].start();
}
}
}
非公平信号量是线程启动的顺序和调用semaphore.acquire()的顺序无关,也就是线程先启动了并不代表获得许可
修改MyService中的代码如下:
private boolean isFair = true;
此现象说明即使是公平信号量,也不能保证线程启动顺序和semaphore.acquire()强一致性
在此,Semaphore常用的API已经基本学习完成了,后续将继续学习Exchanger的使用,加油!!!