Semaphore,位于java.util.concurrent包下面
- Semaphore中管理着一组虚拟的许可,许可的初始数量可通过构造函数来指定
new Semaphore(1);
,执行操作时可以首先获得许可semaphore.acquire();
,并在使用后释放许可semaphore.release();
。如果没有许可,那么acquire方法将会一直阻塞直到有许可(或者直到被终端或者操作超时)。 - Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。
- Semaphore分为单值和多值两种,前者只能被一个线程获得,后者可以被若干个线程获得。
作用:可以用来控制同时访问某个特定资源的操作数量,或者某个操作的数量。
下面使用Semaphore实现两个例子:
- 互斥
大家都学过操作系统,都知道互斥的概念,比较简单的互斥实现,比如PV操作,判断资源,然后忙等实现互斥;上一篇也说过,忙等对CPU的消耗巨大,下面我们通过Semaphore来实现一个比较好的互斥操作:
假设我们公司只有一台打印机,我们需要对这台打印机的打印操作进行互斥控制:
首先是没有使用Semaphore来实现互斥操作
public class Test {
public void print(String str) throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"准备完成,开始打印...");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"正在打印..."+str);
System.out.println(Thread.currentThread().getName()+"打印完成,退出程序 ...");
}
public static void main(String[] args) {
Test t = new Test();
for(int i = 0; i < 10 ; i++) {
new Thread() {
public void run() {
try {
t.print("hello");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
}
}
运行结果:
从结果可以看出,一个Thread还没有打印完成,退出程序,别的Thread就进入开始打印了,这样必然没有按照正确的流程来打印。
使用Semaphore来控制互斥访问数
import java.util.concurrent.Semaphore;
/**
* 使用Semphore实现互斥访问打印机
* @author ghw
*
*/
public class Test {
//定义初始值为1的信号量
private Semaphore semaphore = new Semaphore(1);
//模拟打印机打印操作
public void print(String str) throws InterruptedException {
//请求许可
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"准备完成,开始打印...");
//模拟打印时间
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"正在打印..."+str);
System.out.println(Thread.currentThread().getName()+"打印完成,退出程序 ...");
//释放许可
semaphore.release();
}
public static void main(String[] args) {
Test t = new Test();
//开启10个线程执行打印任务
for(int i = 0; i < 10 ; i++) {
new Thread() {
public void run() {
try {
t.print("hello");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
}
}
运行结果:
从结果可以看出,多个线程虽然是无序执行的,但每个线程执行完成后退出程序才开始执行下一个线程,这样就不会乱。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
public class ConnectPool {
private final List<Conn> pool = new ArrayList<Conn>(3);
private Semaphore semaphore = new Semaphore(3);
public ConnectPool() {
pool.add(new Conn());
pool.add(new Conn());
pool.add(new Conn());
}
public Conn getConn() throws InterruptedException {
semaphore.acquire();
Conn c = null ;
synchronized (pool)
{
c = pool.remove(0);
}
System.out.println(Thread.currentThread().getName()+" 获得一个连接 " + c);
return c ;
}
public void release(Conn c) {
pool.add(c);
System.out.println(Thread.currentThread().getName()+" 释放了一个连接 " + c);
semaphore.release();
}
public static void main(String[] args) {
ConnectPool pool = new ConnectPool();
new Thread() {
public void run() {
try {
Conn c = pool.getConn();
Thread.sleep(3000);
pool.release(c);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
for(int i=0;i<5;i++) {
new Thread() {
public void run() {
try {
Conn c = pool.getConn();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}
}
}
运行结果:
先让Thread-0持有一个连接3秒,然后瞬间让3个线程再去请求分配连接,造成Thread-3一直等到Thread-0对连接的释放,然后获得连接。
通过两个例子,基本已经了解了Semaphore的用法,这里的线程池例子只是为了说明Semaphore的用法,真实的实现代码比这复杂的多,而且可能也不会直接用Semaphore。