之前一直对Semaphore不是很理解,感觉它功能十分简单,用Lock加Condition完全可以轻易替代它,觉得它根本没有存在的必要。最近遇到一个例子,用Semaphore可以优雅的实现,用Lock加Condition却让我大费脑筋,主要原因是自己水平太菜。记录下来,免得以后忘了。
例子:某单位有3台打印机,有10个用户提交打印任务,写程序模拟对打印机的管理。不废话,直接上代码
public class Main {
//主程序,很简单,开启10个线程,每个线程是一个打印任务
//线程间共享资源是PrintQueue,即打印机队列,负责对打印机进行管理
public static void main(String[] args) {
final PrintQueue printQueue = new PrintQueue();
Thread[] threads = new Thread[10];
for(int i=0; i<10; i++) {
threads[i] = new Thread(new Job(printQueue), "线程"+i);
}
for(int i=0; i<10; i++) {
threads[i].start();
}
}
}
//线程的具体实现,也很简单,就是调用PrintQueue方法打印一些内容
//具体打印什么内容都省了,随便模拟一下
public class Job implements Runnable {
private final PrintQueue printQueue;
public Job(final PrintQueue printQueue) {
this.printQueue = printQueue;
}
public void run() {
System.out.printf("%s: 提交打印任务\n",
Thread.currentThread().getName());
printQueue.printJob();
System.out.printf("%s: 向用户汇报:打印完毕!~\n",
Thread.currentThread().getName());
}
}
用信号量Semaphore实现打印机队列
public class PrintQueue {
private final Semaphore semaphore;
private boolean[] freePrinters;
private Lock lockPrinters;
public PrintQueue() {
semaphore = new Semaphore(3);
freePrinters = new boolean[]{true, true, true};
lockPrinters = new ReentrantLock();
}
public void printJob() {
int assignedPrinter = -1;
try {
semaphore.acquire();
assignedPrinter = getPrinter();
long duration = (long)(Math.random()*9) + 1;
System.out.printf("%s: %d 号打印机打印了第1部分内容,耗时 %d 秒\n",
Thread.currentThread().getName(), assignedPrinter,duration);
TimeUnit.SECONDS.sleep(duration);
duration = (long)(Math.random()*9) + 1;
System.out.printf("%s: %d 号打印机打印了第2部分内容,耗时 %d 秒\n",
Thread.currentThread().getName(), assignedPrinter,duration);
TimeUnit.SECONDS.sleep(duration);
duration = (long)(Math.random()*9) + 1;
System.out.printf("%s: %d 号打印机打印了第3部分内容,耗时 %d 秒\n",
Thread.currentThread().getName(), assignedPrinter,duration);
TimeUnit.SECONDS.sleep(duration);
}catch(InterruptedException e) {
e.printStackTrace();
}finally {
releasePrinter(assignedPrinter);
}
}
private int getPrinter() {
int ret = -1;
lockPrinters.lock();
for(int i=0; i<freePrinters.length; i++) {
if(freePrinters[i] == true) {
ret = i;
freePrinters[i] = false;
break;
}
}
lockPrinters.unlock();
return ret;
}
private void releasePrinter(int index) {
freePrinters[index] = true;
semaphore.release();
}
}
改用Lock和Condition实现打印机队列
public class PrintQueue {
private boolean[] freePrinters;
private Lock lockPrinters;
private Condition cond;
public PrintQueue() {
freePrinters = new boolean[]{true, true, true};
lockPrinters = new ReentrantLock();
cond = lockPrinters.newCondition();
}
public void printJob() {
int assignedPrinter = getPrinter();
try {
long duration = (long)(Math.random()*9) + 1;
System.out.printf("%s: %d 号打印机打印了第1部分内容,耗时 %d 秒\n",
Thread.currentThread().getName(), assignedPrinter,duration);
TimeUnit.SECONDS.sleep(duration);
duration = (long)(Math.random()*9) + 1;
System.out.printf("%s: %d 号打印机打印了第2部分内容,耗时 %d 秒\n",
Thread.currentThread().getName(), assignedPrinter,duration);
TimeUnit.SECONDS.sleep(duration);
duration = (long)(Math.random()*9) + 1;
System.out.printf("%s: %d 号打印机打印了第3部分内容,耗时 %d 秒\n",
Thread.currentThread().getName(), assignedPrinter,duration);
TimeUnit.SECONDS.sleep(duration);
}catch(InterruptedException e) {
e.printStackTrace();
}finally {
releasePrinter(assignedPrinter);
}
}
private int getPrinter() {
int ret = -1;
lockPrinters.lock();
while(true) {
for(int i=0; i<freePrinters.length; i++) {
if(freePrinters[i] == true) {
ret = i;
freePrinters[i] = false;
break;
}
}
if(ret>=0) {
break;
}else {
try {
cond.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
lockPrinters.unlock();
return ret;
}
private void releasePrinter(int index) {
lockPrinters.lock();
freePrinters[index] = true;
cond.signalAll();
lockPrinters.unlock();
}
}
比较后可见,使用Lock加Condition需要写更多的代码,而且逻辑上不是特别清晰,Semaphore的使用是十分必要的。
另一种使用Lock加Condition的实现
public class PrintQueue {
private boolean[] freePrinters;
private Lock lockPrinters;
private Lock permitLock;
private Condition cond;
private int printerCount;
public PrintQueue() {
freePrinters = new boolean[]{true, true, true};
lockPrinters = new ReentrantLock();
permitLock = new ReentrantLock();
cond = permitLock.newCondition();
printerCount = 3;
}
public void printJob() {
getPermit();
int assignedPrinter = getPrinter();
try {
long duration = (long)(Math.random()*9) + 1;
System.out.printf("%s: %d 号打印机打印了第1部分内容,耗时 %d 秒\n",
Thread.currentThread().getName(), assignedPrinter,duration);
TimeUnit.SECONDS.sleep(duration);
duration = (long)(Math.random()*9) + 1;
System.out.printf("%s: %d 号打印机打印了第2部分内容,耗时 %d 秒\n",
Thread.currentThread().getName(), assignedPrinter,duration);
TimeUnit.SECONDS.sleep(duration);
duration = (long)(Math.random()*9) + 1;
System.out.printf("%s: %d 号打印机打印了第3部分内容,耗时 %d 秒\n",
Thread.currentThread().getName(), assignedPrinter,duration);
TimeUnit.SECONDS.sleep(duration);
}catch(InterruptedException e) {
e.printStackTrace();
}finally {
releasePrinter(assignedPrinter);
releasePermit();
}
}
private int getPrinter() {
int ret = -1;
lockPrinters.lock();
for(int i=0; i<freePrinters.length; i++) {
if(freePrinters[i] == true) {
ret = i;
freePrinters[i] = false;
break;
}
}
lockPrinters.unlock();
return ret;
}
private void getPermit() {
try {
permitLock.lock();
while(printerCount<=0) {
cond.await();
}
printerCount--;
}catch(Exception e) {
e.printStackTrace();
}finally {
permitLock.unlock();
}
}
private void releasePermit() {
try {
permitLock.lock();
printerCount++;
}catch(Exception e) {
e.printStackTrace();
}finally {
cond.signalAll();
permitLock.unlock();
}
}
private void releasePrinter(int index) {
freePrinters[index] = true; //原子操作,无需同步
}
}