什么是Latch设计模式?
做程序员是个苦差事,并不像外人看到的那么光鲜,加班熬夜是家常便饭的事,Alex在结束了连续一个月的加班之后决定约上几个同为程序员的好朋友(jack,Gavin,Dillon)打算在周末的时候放松一下,他们相约在周六的早上十点在城市广场北口见面,然后一同前往郊区的“同心湖”垂钓、烧烤,最后登上本市的最高建筑‘汇聚塔’,计划了出游路线之后,每个人或乘坐公交,或搭乘地铁,总之他们都会在城市广场北口相会,然后统一去做同一件事,那就是一起前往“同心湖”。
诸如此类的情形在我们的日常生活中也是屡见不鲜,比如若干线程并发执行某个特定的任务,然后等到所有的子任务都执行结束之后再统一汇总,比如用户想要查询自己三年以来银行账号的流水,为了保证运行数据库的数据量在一个恒定的范围内,通常数据只会保存一年的记录,其他的历史记录或被备份到磁盘,或者被存储于hive数据仓库,或者被转存至备份数据库之中,总之想要三年的流水记录,需要若干个渠道的查询才可以汇齐。如果一个线程负责执行这样的任务,则需要经历若干次的查询最后汇总返回给用户,很明显这样的操作性能低下,用户体验差,如果我们将每一个渠道的查询交给一个线程或者若干个线程去查询,然后统一汇总,那么性能会提高跟多,响应时间也会缩短不少。
再回到程序员出游的例子,每个程序员到达城市广场北口的时间不一定,同样到达广场的方式也不尽相同,但是他们都会陆续到达汇集的地点,假设Jack由于堵车迟迟未到,其他三个人只能一直等待,直到Jack到达城市广场北口才能一同前往。 Latch(阀门)设计模式,该模式指定了一个屏障,只有所有的条件都达到满足的时候,门阀才能打开。
CountDownLatch程序实现
package MutilThreadModel.LatchModel; import java.util.concurrent.TimeUnit; /** * Created by JYM on 2019/1/15 * 无限等待的Latch * */ public abstract class Latch { //用于控制多少个线程完成任务时才能打开阀门 protected int limit; //通过构造函数传入limit public Latch(int limit) { this.limit = limit; } //该方法会使得当前线程一直等待,直到所有的线程都完成工作,被阻塞的线程是允许被中断的 public abstract void await() throws InterruptedException; //当任务线程完成工作之后调用该方法使得计数器减一 public abstract void countDown(); //获取当前还有多少个线程没有完成任务 public abstract int getUnarrived(); //增加可超时的抽象方法 public abstract void await(TimeUnit unit,long time) throws InterruptedException,WaitTimeoutException; } /** * 子任务数量达到limit的时候,门阀才能打开,await()方法用于等待所有的子任务完成,如果到达数量未达到limit的时候,将会无限等待 * 下去,当子任务完成的时候调用countDown()方法使计数器减少一个,表明我已经完成任务了,getUnarrived()方法主要用于查询当前有 * 多少个子任务还未结束。 * */
无限等待CountDownLatch的实现
package MutilThreadModel.LatchModel; import java.util.concurrent.TimeUnit; /** * Created by JYM on 2019/1/15 * 实现一个无限制等待门阀打开的Latch实现,当limit>0时调用await方法的线程 * 将会进入无限的等待。 * */ public class CountDownLatch extends Latch { public CountDownLatch(int limit) { super(limit); } @Override public void await() throws InterruptedException { synchronized (this) { //当limit>0,当前线程进入阻塞状态 while (limit>0) { this.wait(); } } } @Override public void countDown() { synchronized (this) { if (limit <= 0) { throw new IllegalStateException("all of task already arrived"); } //使limit减一,并且通知阻塞线程 limit--; this.notifyAll(); } } @Override public int getUnarrived() { //返回有多少线程还未完成任务 return limit; } @Override public void await(TimeUnit unit, long time) throws InterruptedException,WaitTimeoutException { if (time<0) { throw new IllegalArgumentException(" The time is invalid "); } long remainingNanos = unit.toNanos(time); //将time转换为纳秒 //等待任务将在endNanos纳秒后超时 final long endNanos = System.nanoTime()+remainingNanos; synchronized (this) { while (limit>0) { //如果超时则抛出WaitTimeoutException异常 if (TimeUnit.NANOSECONDS.toMillis(remainingNanos) <= 0) { throw new WaitTimeoutException(" The wait time over specify time."); } //等待remainingNanos,在等待的过程中有可能会被中断,需要重新计算remainingNanos this.wait(TimeUnit.NANOSECONDS.toMillis(remainingNanos)); remainingNanos = endNanos-System.nanoTime(); } } } /* * 为了方便计算,我们将所有的时间单都换算成了纳秒,但是Object的wait方法只能够接受纳秒,因此该方法还涉及了时间 * 的换算,另外如果等待剩余时间不足1毫秒,那么将会抛出WaitTimeoutException异常通知等待者。 * */ } /** * 在上述代码中,await()方法不断判断limit的数量,大于0时门阀将不能打开,需要持续等待直到limit数量为0为止; * countDown()方法调用之后会导致limit--操作,并且通知wait中的线程再次判断limit的值是否等于0,当limit被减少 * 到了0以下,则抛出状态非法的异常;getUnarrived()获取当前还有多少个子任务未完成,这个返回值并不一定就是准确 * 的,在多线程的情况下,某个线程在获得Unarrived任务数量并且返回之后,有可能limit又被减少,因此getUnarrived是一个评估值。 * */
package MutilThreadModel.LatchModel; /** * Created by JYM on 2019/1/15 * */ //当子任务线程执行超时的时候将会抛出该异常 public class WaitTimeoutException extends Exception { public WaitTimeoutException(String message) { super(message); } }
程序测试:
package MutilThreadModel.LatchModel; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; /** * Created by JYM on 2019/1/15 * 测试程序 * */ /* * 程序员旅游线程 * */ public class ProgrammerTravel extends Thread { //门阀 private final Latch latch; //程序员 private final String programmer; //交通工具 private final String transportation; //通过构造函数传入latch,programmer,transportation public ProgrammerTravel(Latch latch,String programmer,String transportation) { this.latch = latch; this.programmer = programmer; this.transportation = transportation; } @Override public void run() { System.out.println(programmer+" start take the transportation ["+transportation+"]"); try{ //程序员乘坐交通工具花费在路上的时间(使用随机数字模拟) TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10)); }catch (InterruptedException e) { e.printStackTrace(); } System.out.println(programmer+" arrived by "+transportation); //完成任务时使计数器减一 latch.countDown(); } /* * ProgrammerTravel继承自Thread代表程序员,需要三个构造函数,第一个是前文中设计的latch(门阀),第二个是程序员的名称,比如 * 前文中的Jack、Gavin等,第三个参数则表示他们所搭乘的交通工具。 * TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10))子句在run方法中模拟每个人到达目的地所花费的时间, * 当他们分别到达目的地的时候,需要执行latch.countDown(),使计数器减少一个以标明自己已到达。 * */ // public static void main(String[] args) throws InterruptedException // { // //定义Latch,limit为4 // Latch latch = new CountDownLatch(4); // new ProgrammerTravel(latch,"Alex","Bus").start(); // new ProgrammerTravel(latch,"Gavin","Walking").start(); // new ProgrammerTravel(latch,"Jack","Subway").start(); // new ProgrammerTravel(latch,"Dillon","Bicycle").start(); // //当前线程(main线程会进入阻塞,直到四个程序员全部都到达目的地) // latch.await(); // System.out.println("== all of programmer arrived =="); // } public static void main(String[] args) throws InterruptedException { Latch latch = new CountDownLatch(4); new ProgrammerTravel(latch,"Alex","Bus").start(); new ProgrammerTravel(latch,"Gavin","Walking").start(); new ProgrammerTravel(latch,"Jack","Subway").start(); new ProgrammerTravel(latch,"Dillon","Bicycle").start(); try { latch.await(TimeUnit.SECONDS,5); System.out.println("== all of programmer arrived =="); }catch (WaitTimeoutException e) { e.printStackTrace(); } } }