Semaphore的作用:
在Java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了解决多个线程并发同一资源造成的数据不一致的问题。在另外一种场景下,一个资源有多个副本可供同时使用,比如打印机房有多个打印机、厕所有多个坑可供同时使用,这种情况下,Java提供了另外的并发访问控制--资源的多副本的并发访问控制,今天学习的信号量Semaphore即是其中的一种。
Semaphore实现原理初探:
Semaphore是用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。
如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。
Semaphore的使用:
Semaphore使用时需要先构建一个参数来指定共享资源的数量,Semaphore构造完成后即是获取Semaphore、共享资源使用完毕后释放Semaphore。
Semaphore semaphore = new Semaphore(10,true);
semaphore.acquire();
//do something here
semaphore.release();
下面的代码就是模拟控制商场厕所的并发使用:
import java.util.concurrent.*;importjava.util.concurrent.locks.ReentrantLock;import java.util.*;import static net.mindview.util.Print.*;classResourceManage {private finalSemaphore semaphore;private booleanresourceArray[];private finalReentrantLock lock;publicResourceManage() {this.resourceArray = new boolean[10];//存放资源状态
this.semaphore = new Semaphore(10, true);//控制10个共享资源的使用,使用先进先出的公平模式进行共享;公平模式的信号量,先来的先获得信号量
this.lock = new ReentrantLock(true);//公平模式的锁,先来的先选
for (int i = 0; i < 10; i++) {
resourceArray[i]= true;//初始化为资源可用的情况
}
}public void useResource(int userId) throwsInterruptedException {
semaphore.acquire();try{//semaphore.acquire();
int id = getResourceId();//占到一个资源
System.out.print("userId:" + userId + "正在使用资源,资源id:" + id + "\n");
Thread.sleep(100);//do something,相当于于使用资源
resourceArray[id] = true;//退出这个资源
} catch(InterruptedException e) {
e.printStackTrace();
}finally{
semaphore.release();//释放信号量,计数器加1
}
}private intgetResourceId() {int id = -1;
lock.lock();try{//lock.lock();//虽然使用了锁控制同步,但由于只是简单的一个数组遍历,效率还是很高的,所以基本不影响性能。
for (int i = 0; i < 10; i++) {if(resourceArray[i]) {
resourceArray[i]= false;
id=i;break;
}
}
}catch(Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}returnid;
}
}public class ResourceUser implementsRunnable {privateResourceManage resourceManage;private intuserId;public ResourceUser(ResourceManage resourceManage, intuserId) {this.resourceManage =resourceManage;this.userId =userId;
}public voidrun() {
System.out.print("userId:" + userId + "准备使用资源...\n");try{
resourceManage.useResource(userId);
}catch(InterruptedException e) {//TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print("userId:" + userId + "使用资源完毕...\n");
}public static voidmain(String[] args) {
ResourceManage resourceManage= newResourceManage();
Thread[] threads= new Thread[100];for (int i = 0; i < 100; i++) {
Thread thread= new Thread(new ResourceUser(resourceManage, i));//创建多个资源使用者
threads[i] =thread;
}for (int i = 0; i < 100; i++) {
Thread thread=threads[i];try{
thread.start();//启动线程
} catch(Exception e) {
e.printStackTrace();
}
}
}
userId:2准备使用资源...
userId:6准备使用资源...
userId:4准备使用资源...
userId:0准备使用资源...
userId:3准备使用资源...
userId:1准备使用资源...
userId:8准备使用资源...
userId:9准备使用资源...
userId:10准备使用资源...
userId:1正在使用资源,资源id:5userId:3正在使用资源,资源id:4userId:6正在使用资源,资源id:2userId:0正在使用资源,资源id:3userId:7准备使用资源...
userId:2正在使用资源,资源id:0userId:4正在使用资源,资源id:1userId:5准备使用资源...
userId:13准备使用资源...
userId:7正在使用资源,资源id:9userId:12准备使用资源...
userId:11准备使用资源...
userId:10正在使用资源,资源id:8userId:15准备使用资源...
userId:9正在使用资源,资源id:7userId:8正在使用资源,资源id:6userId:16准备使用资源...
userId:14准备使用资源...
userId:18准备使用资源...
userId:17准备使用资源...
userId:20准备使用资源...
userId:19准备使用资源...
userId:92准备使用资源...
userId:93准备使用资源...
userId:94准备使用资源...
userId:95准备使用资源...
userId:96准备使用资源...
userId:97准备使用资源...
userId:99准备使用资源...
userId:98准备使用资源...
userId:10使用资源完毕...
userId:5正在使用资源,资源id:0userId:12正在使用资源,资源id:2userId:16正在使用资源,资源id:5userId:18正在使用资源,资源id:8userId:17正在使用资源,资源id:9userId:13正在使用资源,资源id:1userId:3使用资源完毕...
userId:0使用资源完毕...
userId:1使用资源完毕...
userId:2使用资源完毕...
userId:4使用资源完毕...
userId:9使用资源完毕...
userId:7使用资源完毕...
userId:6使用资源完毕...
最后,Semaphore除了控制资源的多个副本的并发访问控制,也可以使用二进制信号量来实现类似synchronized关键字和Lock锁的并发访问控制功能。