java并发工具类——Phaser

转载自sithcait的博客

在JAVA 1.7引入了一个新的并发API:Phaser,一个可重用的同步barrier。在此前,JAVA已经有CyclicBarrier、CountDownLatch这两种同步barrier,但是Phaser更加灵活,而且侧重于“重用”。

一、简述
1、注册机制
与其他barrier不同的是,Phaser中的“注册的同步者(parties)”会随时间而变化,Phaser可以通过构造器初始化parties个数,也可以在Phaser运行期间随时加入(register)新的parties,以及在运行期间注销(deregister)parties。运行时可以随时加入、注销parties,只会影响Phaser内部的计数器,它建立任何内部的bookkeeping(账本),因此task不能查询自己是否已经注册了,当然你可以通过实现子类来达成这一设计要求。

//伪代码  
Phaser phaser =new Phaser();  
phaser.register();//parties count: 1  
....  
phaser.arriveAndDeregister()://count : 0;  
....  

CyclicBarrier、CountDownLatch需要在初始化的构造函数中指定同步者的个数,且运行时无法再次调整。

CountDownLatch countDownLatch = new CountDownLatch(12);  
//count deregister parties after all  
//parties count is 12 all the times  
//if you want change the number of parties, you should create a new instance.  
CyclicBarrier cyclicBarrier =new CyclicBarrier(12);  

2、同步机制
类似于CyclicBarrier,Phaser也可以awaited多次,它的arrivedAndAwaitAdvance()方法的效果类似于CyclicBarrier的await()。Phaser的每个周期(generation)都有一个phase数字,phase 从0开始,当所有的已注册的parties都到达后(arrive)将会导致此phase数字自增(advance),当达到Integer.MAX_VALUE后继续从0开始。这个phase数字用于表示当前parties所处于的“阶段周期”,它既可以标记和控制parties的wait行为、唤醒等待的时机。

  • 1)Arrival:
    Phaser中的arrive()、arriveAndDeregister()方法,这两个方法不会阻塞(block),但是会返回相应的phase数字,当此phase中最后一个party也arrive以后,phase数字将会增加,即phase进入下一个周期,同时触发(onAdvance)那些阻塞在上一phase的线程。这一点类似于CyclicBarrier的barrier到达机制;更灵活的是,我们可以通过重写onAdvance方法来实现更多的触发行为。
  • 2)Waiting
    Phaser中的awaitAdvance()方法,需要指定一个phase数字,表示此Thread阻塞直到phase推进到此周期,arriveAndAwaitAdvance()方法阻塞到下一周期开始(或者当前phase结束)。不像CyclicBarrier,即使等待Thread已经interrupted,awaitAdvance方法会继续等待。Phaser提供了Interruptible和Timout的阻塞机制,不过当线程Interrupted或者timout之后将会抛出异常,而不会修改Phaser的内部状态。如果必要的话,你可以在遇到此类异常时,进行相应的恢复操作,通常是在调用forceTermination()方法之后。
    Phaser通常在ForJoinPool中执行tasks,它可以在有task阻塞等待advance时,确保其他tasks的充分并行能力。
  • 3、中断(终止)
    Phaser可以进入Termination状态,可以通过isTermination()方法判断;当Phaser被终止后,所有的同步方法将会立即返回(解除阻塞),不需要等到advance(即advance也会解除阻塞),且这些阻塞方法将会返回一个负值的phase值(awaitAdvance方法、arriveAndAwaitAdvance方法)。当然,向一个termination状态的Phaser注册party将不会有效;此时onAdvance()方法也将会返回true(默认实现),即所有的parties都会被deregister,即register个数为0。
  • 4、Tiering(分层)
    Phaser可以“分层”,以tree的方式构建Phaser来降低“竞争”。如果一个Phaser中有大量parties,这会导致严重的同步竞争,所以我们可以将它们分组并共享一个parent Phaser,这样可以提高吞吐能力;Phaser中注册和注销parties都会有Child 和parent Phaser自动管理。当Child Phaser中中注册的parties变为非0时(在构造函数Phaser(Phaser parent,int parties),或者register()方法),Child Phaser将会注册到其Parent上;当Child Phaser中的parties变为0时(比如由arrivedAndDegister()方法),那么此时Child Phaser也将从其parent中注销出去。
  • 5、监控
    同步的方法只会被register操作调用,对于当前state的监控方法可以在任何时候调用,比如getRegisteredParties()获取已经注册的parties个数,getPhase()获取当前phase周期数等;因为这些方法并非同步,所以只能反映当时的瞬间状态。

二、常用的Barrier比较
1、CountDownLatch

public static void main(String[] args) {
        //创建时,就需要指定参与的parties个数  
        int parties = 12;
        CountDownLatch latch = new CountDownLatch(parties);
        //线程池中同步task  
        ExecutorService executor = Executors.newFixedThreadPool(parties);
        for (int i = 0; i < parties; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        //可以在任务执行开始时执行,表示所有的任务都启动后,主线程的await即可解除  
                        //latch.countDown();  
                        //run  
                        //..  
                        Thread.sleep(3000);
                    } catch (Exception e) {
                    } finally {
                        //任务执行完毕后:到达  
                        //表示所有的任务都结束,主线程才能继续  
                        latch.countDown();
                    }
                }
            });
        }
        latch.await();//主线程阻塞,直到所有的parties到达  
        //latch上所有的parties都达到后,再次执行await将不会有效,  
        //即barrier是不可重用的  
        executor.shutdown();
}

2、CyclicBarrier

public static void main(String[] args) {
        //创建时,就需要指定参与的parties个数
        int parties = 12;
        CyclicBarrier barrier =new CyclicBarrier(parties);
        //线程池中同步task
        ExecutorService executor = Executors.newFixedThreadPool(parties);
        for(int i = 0; i < parties; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        int i = 0;
                        while (i < 3 && !barrier.isBroken()) {
                            System.out.println("generation begin:" + i + ",tid:" + Thread.currentThread().getId());
                            Thread.sleep(3000);
                            //如果所有的parties都到达,则开启新的一次周期(generation)
                            //barrier可以被重用
                            barrier.await();
                            i++;
                        }
                    }catch (Exception e) {
                        e.printStackTrace();
                    }
                    finally {
                    }
                }
            });
        }
        Thread.sleep(100000);
    }
}

3、Phaser

public static void main(String[] args) {
        //创建时,就需要指定参与的parties个数
        int parties = 3;
        //可以在创建时不指定parties
        // 而是在运行时,随时注册和注销新的parties
        Phaser phaser =new Phaser();
        //主线程先注册一个
        //对应下文中,主线程可以等待所有的parties到达后再解除阻塞(类似与CountDownLatch)
        phaser.register();
        ExecutorService executor = Executors.newFixedThreadPool(parties);
        for(int i = 0; i < parties; i++) {
            phaser.register();//每创建一个task,我们就注册一个party
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        int i = 0;
                        while (i < 3 && !phaser.isTerminated()) {
                            System.out.println("Generation:" + phaser.getPhase() + " " + "currentThread:" + Thread.currentThread().getName());
                            Thread.sleep(3000);
                            //等待同一周期内,其他Task到达
                            //然后进入新的周期,并继续同步进行
                            phaser.arriveAndAwaitAdvance();
                            System.out.println("registeredParties:" + phaser.getRegisteredParties());
                            System.out.println("arrivedParties:" + phaser.getArrivedParties());
                            i++;//我们假定,运行三个周期即可
                        }
                    }catch (Exception e) {
                    }
                    finally {
                        phaser.arriveAndDeregister();
                    }
                }
            });
        }

        //主线程到达,且注销自己
        //此后线程池中的线程即可开始按照周期,同步执行。
        phaser.arriveAndDeregister();
   }
}

三、使用phaser解决并发编程问题
leetcode上的h2o生成问题
题目描述
有 oxygen (氧)和 hydrogen(氢) 两个线程,你的目标是把它们分组生成水分子。当然这里有道闸门(barrier)在水分子生成之前不得不等待。oxygen 和 hydrogen 线程每三个会被分成一组,包括两个hydrogen 线程和一个oxygen,它们三个每个都会产生一个对应的原子组合成水分子。你必须保证当前分组内的各个线程提供的原子组合成一个水分子之后这些线程才能参与产生下一个水分子。

代码

class Scratch {
    public static void main(String[] args) throws Exception {
        Map validResult = new HashMap();
        validResult.put("HhO", true);
        validResult.put("HOh", true);
        validResult.put("OHh", true);
        validResult.put("OhH", true);
        validResult.put("hOH", true);
        validResult.put("hHO", true);
        int count = 100;
        H2O h2o = new H2O();
        StringBuffer sb = new StringBuffer();
        Runnable releaseHydrogen1 = () -> sb.append("H");
        Runnable releaseHydrogen2 = () -> sb.append("h");
        Runnable releaseOxygen = () -> sb.append("O");
        HydrogenGenerator h1 = new HydrogenGenerator(count, h2o, releaseHydrogen1);
        HydrogenGenerator h2 = new HydrogenGenerator(count, h2o, releaseHydrogen2);
        OxygenGenerator o = new OxygenGenerator(count, h2o, releaseOxygen);
        h1.start();
        o.start();
        Thread.sleep(1000);
        h2.start();
        h1.join();
        h2.join();
        o.join();
        System.out.println(sb.toString());
        for (int i = 0; i < (count - 1) * 3; i += 3) {
            String s = sb.substring(i, i + 3);
            if (validResult.get(s) == null) {
                System.out.println("expect (H && h && O) but got " + s);
            }
        }
        System.out.println("pass valid");
    }

    static class H2O{
        private Phaser phaser;
        public H2O() {
            phaser = new Phaser(3);
        }
        public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
            releaseHydrogen.run();
            phaser.arriveAndAwaitAdvance();
        }
        public void oxygen(Runnable releaseOxygen) throws InterruptedException {
            releaseOxygen.run();
            phaser.arriveAndAwaitAdvance();
        }
    }

    static class HydrogenGenerator extends Thread {
        int n;
        H2O h2o;
        Runnable releaseHydrogen;
        Random rand = new Random(System.currentTimeMillis());
        public HydrogenGenerator(int n, H2O h2o, Runnable releaseHydrogen) {
            this.n = n;
            this.h2o = h2o;
            this.releaseHydrogen = releaseHydrogen;
        }
        @Override
        public void run() {
            for (; n >= 0; n--) {
                try {
                    Thread.sleep(rand.nextInt(100));
                    h2o.hydrogen(releaseHydrogen);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class OxygenGenerator extends Thread {
        int n;
        H2O h2o;
        Runnable releaseOxygen;
        Random rand = new Random(System.currentTimeMillis());
        public OxygenGenerator(int n, H2O h2o, Runnable releaseOxygen) {
            this.n = n;
            this.h2o = h2o;
            this.releaseOxygen = releaseOxygen;
        }
        @Override
        public void run() {
            for (; n >= 0; n--) {
                try {
                    Thread.sleep(rand.nextInt(100));
                    h2o.oxygen(releaseOxygen);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

可以看到只需要一个同步器phaser就可以解决这个看似复杂的问题。phaser特别适合应付这种多线程协作分阶段提交的情况,之后工作如果遇到类似的问题可以优先考虑使用它解决。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值