【并发编程】CountDownLatch、CyclicBarrier 等同步器

CountDownLatch

使一个线程等待其他线程都执行完后再执行。
通过构造器初始化要等待的线程数,每个线程执行后调用 countDown() 方法使其计数器减一
调用 await() 方法的线程会被挂起,直到计数器减为0自动执行

public static void main(String[] args) throws ExecutionException, InterruptedException {

    CountDownLatch latch = new CountDownLatch(5);
    for (int i = 1; i <= 5; i++) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
            latch.countDown();
        }, String.valueOf(i)).start();
    }
    latch.await();
    System.out.println("end");
}

还可以设置等待时间,超过等待时间count还没变为0的话也继续执行

public boolean await(long timeout, TimeUnit unit)

CyclicBarrier

多个线程互相等待,直到到达同一个同步点,再继续一起执行。
通过构造器初始化一个阀门值和所有线程到达阀门后所要执行的线程任务,每个线程执行后调用 await() 使计数器加一,在计数器没达到阀门时所有线程都将等待,已经打开的屏障又有新线程加入的话还是会重新生效(循环)

public static void main(String[] args) throws InterruptedException {
    int parties = 5;
    ExecutorService executor = Executors.newFixedThreadPool(10);
    CyclicBarrier cyclicBarrier = new CyclicBarrier(parties, () -> {
        System.out.println("已集齐【" + parties + "】个木头");
    });

    for (int i = 1; i <= 10; i++) {
        TimeUnit.SECONDS.sleep(1);
        final int temp = i;
        executor.submit(() -> {
            try {
                System.out.println("线程" + Thread.currentThread().getName() + " 已砍伐木头【x" + temp + "】");
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
    }
    TimeUnit.SECONDS.sleep(15);
    executor.shutdownNow();
}

CountDownLatch和CyclicBarrier的区别

CountDownLatchCyclicBarrier
减记数方式加记数方式
count为0时释放等待线程count达到阀门值时释放所有等待线程
一次性的可循环利用
基于AQS实现基于ReentrantLock锁和Condition实现

Semaphore

信号量,维持一组许可证。 通过 acquire() 获取许可证,如果没有可用的许可证就阻塞。通过 release() 释放许可证,潜在地释放阻塞方。

主要是用于多个共享资源的互斥使用;另一方面用于并发线程数量的控制(限流)

public static void main(String[] args) {
    Semaphore semaphore = new Semaphore(2);

    ExecutorService executor = Executors.newFixedThreadPool(6);

    for (int i = 1; i <= 10; i++) {
        executor.submit(() -> {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " 占位");
                TimeUnit.SECONDS.sleep(RandomUtil.randomInt(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + " 离开");
                semaphore.release();
            }
        });
    }
}

Phaser

jdk1.7出的同步工具类:阶段器,有点像结合了CountDownLatch合CyclicBarrier,用来解决多个线程分阶段共同完成任务的情景问题。

在Phaser内有2个重要状态,分别是phaseparty
phase就是阶段,当所有线程都调用了arrive*方法后,也就是当前阶段任务完成phase+1进入下一阶段任务,party就是当前管理的线程数。
可以通过继承Phaser类重写其onAdvance方法来实现自己的阶段管理,此方法返回true时即终止Phaser。

几个方法

// 初始化计数器,也就是完成当前阶段任务需要执行的线程数
public int bulkRegister(int parties)
// 到达phase,无需等待其他线程到达即开始执行下一阶段任务
public int arrive()
// 到达phase,阻塞等待其他线程到达才开始执行下一阶段任务
public int arriveAndAwaitAdvance()
// 到达phase,并且注销,也就是减少parties数
public int arriveAndDeregister()
// 当所有任务都到达phase后执行该方法,phase代表当前阶段 范围0~Integer.MAX_VALUE,registeredParties代表当前注册的线程数,该方法返回true时结束phaser
protected boolean onAdvance(int phase, int registeredParties)

案例

一对新人结婚办酒席邀请了五位宾客参加。酒席分为四阶段进行:第一阶段等待所有宾客到齐,第二阶段所有宾客到齐后开始吃饭,第三阶段所有宾客都酒足饭饱后陆续离开,第四阶段等所有宾客都离开了新人入洞房。每个阶段都必须等待上个阶段的任务完成后才得以进行。

public class PhaserDemo {

    static Random r = new Random();
    static MarriagePhaser phaser = new MarriagePhaser();

    static void milliSleep(){
        try {
            TimeUnit.SECONDS.sleep(r.nextInt(5));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
	
    static class Person implements Runnable{

        private final String name;

        public Person(String name) {
            this.name = name;
        }

        public void arrive(){
            milliSleep();
            System.out.println(name + " 到达现场!");
            // 只有每个线程都执行了arriveAndAwaitAdvance才会使phase+1进入下一阶段
            phaser.arriveAndAwaitAdvance();
        }

        public void eat(){
            milliSleep();
            System.out.println(name + " 吃饭!");
            phaser.arriveAndAwaitAdvance();
        }

        public void leave(){
            if (name.startsWith("p")) {
                milliSleep();
                System.out.println(name + " 离席!");
                // arriveAndDeregister代表到达并且使parties-1
                phaser.arriveAndDeregister();
            }else {
                phaser.arriveAndAwaitAdvance();
            }
        }

        public void hug(){
            if (!name.startsWith("p")) {
                milliSleep();
                System.out.println(name + " 洞房!");
                phaser.arriveAndDeregister();
            }
        }

        @Override
        public void run() {
            arrive();
            eat();
            leave();
            hug();
        }
    }

    static class MarriagePhaser extends Phaser{

        // 每个阶段完成后都会进行回调  phase代表当前阶段  registeredParties代表注册的parties(线程)数 返回true时Phaser被终止
        @Override
        protected boolean onAdvance(int phase, int registeredParties) {

            switch (phase){
                case 0:
                    System.out.println("所有人都到齐了!" + registeredParties);
                    return false;
                case 1:
                    System.out.println("所有人都吃完了!" + registeredParties);
                    return false;
                case 2:
                    System.out.println("宾客都离开了!" + registeredParties);
                    return false;
                case 3:
                    System.out.println("送入洞房" + registeredParties);
                    return true;
                default:
                    return true;
            }
        }
    }

    public static void main(String[] args) {
    	// 初始化Parties的个数
        phaser.bulkRegister(7);
        // 五个宾客线程,一个新郎一个新娘
        for (int i = 0; i < 5; i++) {
            new Thread(new Person("p" + i)).start();
        }
        new Thread(new Person("新郎")).start();
        new Thread(new Person("新娘")).start();
    }
}

Exchanger

交换器,两个线程间交换数据用的。只能两两交换,只有交换完了才会继续玩下执行,否则就一直阻塞。

Exchanger<String> exchanger = new Exchanger<>();
new Thread(() -> {
            String s = "T1";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);
        }, "T1").start();

        new Thread(() -> {
            String s = "T2";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);
        }, "T2").start();

LockSupport

不需要加锁即可实现线程的阻塞,提供了parkunpark两个方法来阻塞和解除阻塞线程,这是一个较为底层 的同步工具类,例如AQS就是调用它,在大多数并发控制的代码中并不会应用到它。

public static void main(String[] args) {
    Thread thread = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                LockSupport.park();
            }
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }
    });
    thread.start();
    // 可以先于park进行,这样以上代码就不会阻塞
    LockSupport.unpark(thread);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值