1. 信号量Semaphore
信号量维护了一组许可证,以约束访问被限制资源的线程数。
类java.util.concurrent.Semaphore实现了信号量。
这段文字转自:https://blog.csdn.net/zbc1090549839/article/details/53389602
在java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了解决多个线程并发同一资源造成的数据不一致的问题。在另外一种场景下,一个资源有多个副本可供同时使用,比如打印机房有多个打印机、厕所有多个坑可供同时使用,这种情况下,Java提供了另外的并发访问控制--资源的多副本的并发访问控制,今天学习的信号量Semaphore即是其中的一种。
Semaphore是用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。
如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。
就好比一个厕所管理员,站在门口,只有厕所有空位,就开门允许与空侧数量等量的人进入厕所。多个人进入厕所后,相当于N个人来分配使用N个空位。为避免多个人来同时竞争同一个侧卫,在内部仍然使用锁来控制资源的同步访问。
2.信号量的使用,常用方法
(1)Semaphore(int permits)构造函数
初始化一个信号量,其中permits指定了可用许可证的数量。
(2)Semaphore(int permits, boolean fair)构造函数
初始化一个信号量,其中permits指定了可用许可证的数量,fair设置公平策略。
当公平策略设置为false,信号量不会保证线程获取许可证的顺序,也就是说,抢占是允许的。
当公平策略设置为true,信号量就能保住调用acquire()方法的任意线程能按照方法被调用处理的顺序获取许可证(先进先出,FIFO)。
(3)void acquire()
从这个信号量中获取一个许可证,否则阻塞直到有一个许可证可用或者调用线程被中断。
(4)void acquire(int permits)
从这个信号量中获取permits数量的许可证,否则阻塞直到有一个许可证可用或者调用线程被中断。
(5)int availablePermits()
返回当前可用许可证的数目。
(6)int drainPermits()
获取并返回立即可用的许可证的数量。
(7)void release()
释放一个许可证,将其放回给信号量。可用许可证的数目增加1.
(8)void release(int permits)
释放permits数量的许可证,将其放回给信号量。可用许可证的数目增加permits个.
3. 示例
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args)
{
Semaphore semaphore = new Semaphore(3, true);
Random random = new Random();
Runnable r = new Runnable() {
@Override
public void run()
{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+ "开始工作!");
work();
System.out.println(Thread.currentThread().getName()+ "工作结束!");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
semaphore.release(); //在使用完之后要释放许可证
}
}
void work()
{
int worktime = random.nextInt(1000);
System.out.println(Thread.currentThread().getName()+"工作时间:"+ worktime);
try {
Thread.sleep(worktime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0; i < 6; i++)
{
executorService.execute(r);
}
executorService.shutdown();
}
}
运行结果:
pool-1-thread-1开始工作!
pool-1-thread-1工作时间:319
pool-1-thread-4开始工作!
pool-1-thread-4工作时间:503
pool-1-thread-2开始工作!
pool-1-thread-2工作时间:954
pool-1-thread-1工作结束!
pool-1-thread-3开始工作!
pool-1-thread-3工作时间:996
pool-1-thread-4工作结束!
pool-1-thread-5开始工作!
pool-1-thread-5工作时间:827
pool-1-thread-2工作结束!
pool-1-thread-6开始工作!
pool-1-thread-6工作时间:857
pool-1-thread-3工作结束!
pool-1-thread-5工作结束!
pool-1-thread-6工作结束!
刚开始,只有线程1,4,2在开始工作。只有当线程1结束,释放一个许可证之后,线程3才开始工作。线程4结束工作释放许可证之后,线程5就可以开始工作了。也就是说同一时间段只能有3个许可证可用。