(八)java多线程之Semaphore
本人邮箱: kco1989@qq.com
欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kco
github: https://github.com/kco1989/kco
代码已经全部托管github有需要的同学自行下载
引言
这节课,我们就开始讲一下信号量Semaphore
理论
Semaphore
:一个可计数的信号量。一般,一个semaphore
信号量是一组许可证。如果必要,那个每次acquire
获取许可都是阻塞的,直接一个许可证是可用的,并获取到。每次release
释放,都会增加一个许可证,潜在的,也会释放一个阻塞请求。然而。并非每次许可对象都可以被使用的,这个Semaphore
信号量只保存几个可用的许可证和相应的操作。
如果有几个线程数要访问几个共享资源的话,那么这时候就应该使用信号量。举例说明:这个有类Pool
类,它就使用信号量在控制多线程去访问那么几个有限items
。
class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
// Not a particularly efficient data structure; just for demo
protected Object[] items = ... whatever kinds of items being managed
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null; // not reached
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}
}
在每个线程获取
item
之前,必须先从信号量获取许可。保证这个item
对用户来说是可以使用的。当线程结束使用item
时,并让item
返回item池,这信号量会释放这个许可,之后允许使用线程可以获取到这个item
。必须要注意的,在程序中,在获取许可和释放许可的死胡同并没有使用同步锁,信号量封装了限制对池的访问所需的同步,与维护池本身的一致性所需的任何同步。
Semaphore(int permits)
: 创建一个指定数量的许可的信号量Semaphore(int permits, boolean fair)
创建一个指定数量的许可,并保证每个线程都是公平的,当fair
为true
时,信号量会安装先进先出的原则来获取许可.acquire()
在当前信号量中获取一个许可.当前线程会一直阻塞直到有一个可用的许可,或被其他线程中断.acquireUninterruptibly()
: 在当前信号量中获取一个许可.当前线程会一直阻塞直到有一个可用的许可.tryAcquire()
在当前信号量尝试获取一个许可,如果有可用,则获取到这个许可,并立即返回true
,后缀立即返回false
tryAcquire
在当前信号量获取一个许可,当前线程会一直阻塞直到有一个可用的许可.或指定时间超时了,或被其他线程中断.release()
释放一个许可,把让它返回到这个信号量中.acquire(int permits)
请求指定数量的许可,如果有足够的许可可用,那么当前线程会立刻返回,如果许可不足,则当前会一直等待,直到被其他线程中断,或获取到足够的许可.acquireUninterruptibly(int permits)
请求指定数量的许可,如果有足够的许可可用,那么当前线程会立刻返回,如果许可不足,则当前会一直等待,直到获取到足够的许可.tryAcquire(int permits)
在当前信号量尝试获取指定数量的许可,如果有可用,则获取到这个许可,并立即返回true
,后缀立即返回false
tryAcquire(int permits, long timeout, TimeUnit unit)
在指定的超时时间,当前信号量尝试获取指定数量的许可,如果有可用,则获取到这个许可,并立即返回true
,后缀立即返回false
release(int permits)
释放指定数量的许可availablePermits()
返回当前信号量还有几个可用的许可drainPermits()
请求并立即返回当前信号量可用的全部许可reducePermits(int reduction)
根据指定的缩减量减小可用许可的数目。此方法在使用信号量来跟踪那些变为不可用资源的子类中很有用。此方法不同于 acquire,在许可变为可用的过程中,它不会阻塞等待。isFair()
返回当前的信号量时候是公平的hasQueuedThreads()
查询是否有线程正在等待获取。注意,因为同时可能发生取消,所以返回 true 并不保证有其他线程等待获取许可。此方法主要用于监视系统状态。getQueueLength()
返回正在等待获取的线程的估计数目。该值仅是估计的数字,因为在此方法遍历内部数据结构的同时,线程的数目可能动态地变化。此方法用于监视系统状态,不用于同步控制。getQueuedThreads()
返回一个 collection,包含可能等待获取的线程。因为在构造此结果的同时实际的线程 set 可能动态地变化,所以返回的 collection 仅是尽力的估计值。所返回 collection 中的元素没有特定的顺序。此方法用于加快子类的构造速度,提供更多的监视设施。
例子
看了前面那么方法的介绍,恐怕你想吐的的心都有了吧?还是让我们回归轻松愉快的例子来吧.这里我们还是继续举小明
和小红
谈人生和理想的例子.之前他们在卧室里谈了好几百毫秒的人生和理想.顿时都感觉身疲惫,感觉身体好像被掏空了一样.所以这里他们都想洗一个热水澡,但是沐浴室只有三间,那就抢吧..ok,开始编程...
- 首先,先编写一个沐浴室
ShowerRoom
public class ShowerRoom {
private static final int MAX_SIZE = 3;
Semaphore semaphore = new Semaphore(MAX_SIZE);
public void bathe(String name){
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 洗唰唰啊..洗唰唰... ");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + " 终于洗完澡了...");
semaphore.release();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 然后编写让
小明
和小红
去洗澡操作BoyAndGril
public class BoyAndGril implements Runnable{
ShowerRoom showerRoom;
public BoyAndGril(ShowerRoom showerRoom) {
this.showerRoom = showerRoom;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
showerRoom.bathe(name);
}
}
- 最后,测试一下
public class TestMain {
public static void main(String[] args) {
Set<Thread> boyAndGril = new HashSet<>();
ShowerRoom showerRoom = new ShowerRoom();
for (int i = 0; i < 10; i ++){
boyAndGril.add(new Thread(new BoyAndGril(showerRoom), "小明" + i + "号"));
}
for (int i = 0; i < 10; i ++){
boyAndGril.add(new Thread(new BoyAndGril(showerRoom), "小红" + i + "号"));
}
for (Thread thread : boyAndGril){
thread.start();
}
}
}
运行一下结果
小红3号 洗唰唰啊..洗唰唰...
小红6号 洗唰唰啊..洗唰唰...
小明0号 洗唰唰啊..洗唰唰...
小红3号 终于洗完澡了...
小红2号 洗唰唰啊..洗唰唰...
小红6号 终于洗完澡了...
小明2号 洗唰唰啊..洗唰唰...
小明0号 终于洗完澡了...
小红1号 洗唰唰啊..洗唰唰...
小红2号 终于洗完澡了...
小明5号 洗唰唰啊..洗唰唰...
小明2号 终于洗完澡了...
小明7号 洗唰唰啊..洗唰唰...
小红1号 终于洗完澡了...
小红0号 洗唰唰啊..洗唰唰...
小明5号 终于洗完澡了...
小明7号 终于洗完澡了...
小明4号 洗唰唰啊..洗唰唰...
小红4号 洗唰唰啊..洗唰唰...
小红0号 终于洗完澡了...
小明3号 洗唰唰啊..洗唰唰...
小明4号 终于洗完澡了...
小明9号 洗唰唰啊..洗唰唰...
小红4号 终于洗完澡了...
小红7号 洗唰唰啊..洗唰唰...
小明3号 终于洗完澡了...
小红5号 洗唰唰啊..洗唰唰...
小红5号 终于洗完澡了...
小红9号 洗唰唰啊..洗唰唰...
小红7号 终于洗完澡了...
小明6号 洗唰唰啊..洗唰唰...
小明9号 终于洗完澡了...
小明1号 洗唰唰啊..洗唰唰...
小红9号 终于洗完澡了...
小红8号 洗唰唰啊..洗唰唰...
小明1号 终于洗完澡了...
小明8号 洗唰唰啊..洗唰唰...
小明6号 终于洗完澡了...
小红8号 终于洗完澡了...
小明8号 终于洗完澡了...
ok,运行正常,程序中不会发生四个人以及四个以上的人在同时洗澡的情况.
如果有人觉得这个好像也没有使用什么共享资源啊,没有上面那个例子的item pool
,那行,那把有关semaphore
的代码注释掉,再运行一下.
小红3号 洗唰唰啊..洗唰唰...
小红6号 洗唰唰啊..洗唰唰...
小明0号 洗唰唰啊..洗唰唰...
小红2号 洗唰唰啊..洗唰唰...
小明2号 洗唰唰啊..洗唰唰...
小红1号 洗唰唰啊..洗唰唰...
小明5号 洗唰唰啊..洗唰唰...
小明7号 洗唰唰啊..洗唰唰...
小红0号 洗唰唰啊..洗唰唰...
小明4号 洗唰唰啊..洗唰唰...
小红4号 洗唰唰啊..洗唰唰...
小明3号 洗唰唰啊..洗唰唰...
小明9号 洗唰唰啊..洗唰唰...
小红7号 洗唰唰啊..洗唰唰...
小红5号 洗唰唰啊..洗唰唰...
小红9号 洗唰唰啊..洗唰唰...
小明6号 洗唰唰啊..洗唰唰...
小明1号 洗唰唰啊..洗唰唰...
小红8号 洗唰唰啊..洗唰唰...
小明8号 洗唰唰啊..洗唰唰...
小红3号 终于洗完澡了...
小红2号 终于洗完澡了...
小明2号 终于洗完澡了...
小红6号 终于洗完澡了...
小明0号 终于洗完澡了...
小明5号 终于洗完澡了...
小红0号 终于洗完澡了...
小明7号 终于洗完澡了...
小红1号 终于洗完澡了...
小明4号 终于洗完澡了...
小红4号 终于洗完澡了...
小明3号 终于洗完澡了...
小明9号 终于洗完澡了...
小红8号 终于洗完澡了...
小红5号 终于洗完澡了...
小红9号 终于洗完澡了...
小明6号 终于洗完澡了...
小明1号 终于洗完澡了...
小红7号 终于洗完澡了...
小明8号 终于洗完澡了...
发现这几十个人都同时在三间沐浴室里洗澡,那么肯定有只是一间会出现两人或两人以上同时洗澡的情况.如果浴室够大,大家都没有意见,那还好.就是如果肥皂掉了,这个时候,小明
就得考虑要不要弯腰去捡了....
打赏
如果觉得我的文章写的还过得去的话,有钱就捧个钱场,没钱给我捧个人场(帮我点赞或推荐一下)