线程同步辅助类semaphore笔记
Semaphore/信号,用来控制一个或多个共享资源访问。
例子(Java7并发编程,略做改动):有3台打印机(看作一个资源池),多个客户端请求打印,显然每台打印机一次只能处理一个打印请求。
1.打印机队列实现
//打印队列,同时支持3台打印机 package java7.lesson3_SemaphoreEx; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import util.Util; public class PrintQueue { private boolean[] freePrinters; private Lock lockPrinters;// 该锁用于控制打印机队列的访问 private final Semaphore semaphore;// 信号量,保护对打印队列的访问 private final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss "); public PrintQueue() { this.freePrinters = new boolean[3]; for (int i = 0; i < 3; i++) { freePrinters[i] = true; } this.semaphore = new Semaphore(3); lockPrinters = new ReentrantLock(); } public void printJob(Object document) { try { Util.sop(df.format(new Date())+Thread.currentThread().getName() + ":" + "申请资源..."); semaphore.acquire(); int assignedPrinter = getPrinter(); Util.sop(df.format(new Date())+Thread.currentThread().getName() + ":" + "申请到"+ assignedPrinter + "号打印机,输出打印..."); Thread.sleep((long) (Math.random() * 5000)); freePrinters[assignedPrinter] = true; } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } } private int getPrinter() { int ret = -1; try { lockPrinters.lock(); for (int i = 0; i < 3; i++) { if (freePrinters[i]) { freePrinters[i] = false; ret = i; break; } } } catch (Exception e) { } finally { lockPrinters.unlock(); } return ret; } }
说明:1.信号量semaphore用以控制访问许可,资源池(3个打印机)某时刻只能容纳3个线程访问,其它线程处于阻塞状态,等待获得资源的某个线程释放许可,才能进入。
2. 线程在阻塞获得许可的这段时间中,可能会被中断,所以acquire()方法会抛出InterruptedException。
3. lockPrinters用来控制对资源池的访问,getPrinter()需要同步,避免资源使用混乱,这里用一个boolean[]标记。信号量并未锁住任何资源,资源需要另外加以控制。
4.尤其要注意的是:semaphore是一个相当灵活的使用方式,semaphore.release()之前,并不需要线程先semaphore.acquire(),二者可以不配对出现,A线程获得的许可有可能在B线程释放。换句话说semaphore与线程没有关联。同时灵活性还体现在semaphore不受限于创建时候的初始许可数(这里是3),灵活的代价是我们需要更加注意代码质量。
2.模拟打印请求
package java7.lesson3_SemaphoreEx; public class Job implements Runnable{ private PrintQueue printQueue; public Job(PrintQueue printQueue){ this.printQueue = printQueue; } @Override public void run() { printQueue.printJob(new Object()); } }
3.测试
package java7.lesson3_SemaphoreEx; public class Client { public static void main(String[] args) { PrintQueue printQueue = new PrintQueue(); for (int i=0;i<10;i++){ Job job = new Job(printQueue); Thread thread = new Thread(job,i+"号线程"); thread.start(); } } }
测试结果如下:
2014-08-30 12:39:53 0号线程:申请资源... 2014-08-30 12:39:53 0号线程:申请到0号打印机,输出打印... 2014-08-30 12:39:53 2号线程:申请资源... 2014-08-30 12:39:53 2号线程:申请到1号打印机,输出打印... 2014-08-30 12:39:53 3号线程:申请资源... 2014-08-30 12:39:53 4号线程:申请资源... 2014-08-30 12:39:53 4号线程:申请到2号打印机,输出打印... 2014-08-30 12:39:53 6号线程:申请资源... 2014-08-30 12:39:53 8号线程:申请资源... 2014-08-30 12:39:53 5号线程:申请资源... 2014-08-30 12:39:53 1号线程:申请资源... 2014-08-30 12:39:53 7号线程:申请资源... 2014-08-30 12:39:53 9号线程:申请资源... 2014-08-30 12:39:54 3号线程:申请到0号打印机,输出打印... 2014-08-30 12:39:57 6号线程:申请到2号打印机,输出打印... 2014-08-30 12:39:58 8号线程:申请到1号打印机,输出打印... 2014-08-30 12:39:59 5号线程:申请到2号打印机,输出打印... 2014-08-30 12:39:59 1号线程:申请到0号打印机,输出打印... 2014-08-30 12:40:02 7号线程:申请到1号打印机,输出打印... 2014-08-30 12:40:04 9号线程:申请到2号打印机,输出打印...
输出结果符合预期:前3个线程几乎同时获得申请许可,进入资源池,后面的阻塞直到有空位出现。
另外,还有几点值得了解:
1.与acquire()的阻塞效果不同,Semaphore提供了 boolean tryAcquire()方法,如果不能获得许可,马上返回false,避免了长时间等待,某些场景比较有用。
2.Semaphore提供了第2个参数
public Semaphore(int permits, boolean fair) { sync = (fair)? new FairSync(permits) : new NonfairSync(permits); }
在acquire()处阻塞的线程队列中,一旦有新许可出现,让谁先上?如果fair=true, 那就FIFO,如果是false,那就无序了.默认是false,效率比较高一点。(公平锁是如何实现的,大家可以查看源码和大神的讲解,CLH队列锁还是很有意思的,我止步于应用了)
和这个例子很相似的一个场景大家应该很熟悉,数据库连接池,连接池中没有空闲资源的时候,线程应该继续等待而不是急匆匆返回失败吧。
下一篇: JAVA并发控制的几种办法
推荐阅读
-
C#线程学习笔记五:线程同步--事件构造
-
Java多线程同步工具类之Semaphore
-
Java分享笔记:创建多线程 & 线程同步机制
-
C#线程学习笔记六:线程同步--信号量和互斥体
-
JUC——线程同步辅助工具类(Semaphore,CountDownLatch,CyclicBarrier)
-
JUC——线程同步辅助工具类(Exchanger,CompletableFuture)
-
C#通过Semaphore类控制线程队列的方法
-
C#多线程编程之使用ReaderWriterLock类实现多用户读与单用户写同步的方法
-
CyclicBarrier-同步辅助类
-
JAVA并发编程(三):同步的辅助类之闭锁(CountDownLatch)与循环屏障(CyclicBarrier)