什么是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();
}
}
}