Latch设计模式

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

猜你喜欢

转载自blog.csdn.net/leying521/article/details/86501718
今日推荐