Java并发协作控制之同步辅助类

Java并发协作控制之同步辅助类

1,Latch

1.1,Latch的概念

  latch,英文原意是插销,它是一个同步辅助类,用来同步执行任务中一个或多个线程。它不是用来保护临界区或者共享资源,是用来协调各个线程执行到某个地方的时候,大家都暂停以下,等其他线程都到了,大家在往下走。

Latch的主要实现类:CountDownLatch,两个主要方法

  • countDown() :计数器 -1
  • await():等待 latch 变成 0

1.2,CountDownLatch使用

  例如:百米赛跑比赛,发令枪发出信号后选手开始跑,全部选手跑到终点后比赛结束。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int runnerCount = 10;
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(runnerCount);

        for (int i = 0; i < runnerCount; i++) {
            new Thread(new Worker(startSignal, doneSignal)).start();
        }

        System.out.println("准备工作...");
        System.out.println("准备工作就绪");
        // startSignal-1 => startSignal = 0;
        // Latch变成0后,将唤醒所有在此Latch上await的线程
        startSignal.countDown();
        System.out.println("比赛开始");
        doneSignal.await();
        System.out.println("比赛结束");
    }

    static class Worker implements Runnable {
        // 每个线程都有一个startSignal和doneSignal
        // 它们都分别指向 同一个new CountDownLatch(1)对象和new CountDownLatch(runnerCount)对象
        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;

        Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
        }

        @Override
        public void run() {
            try {
                // 10个线程启动后 将会在此处等待,等待startSignal变成0
                startSignal.await();
                doWork();
                // doneSignal -1
                doneSignal.countDown();
            } catch (InterruptedException e) { }
        }

        void doWork() {
            System.out.println(Thread.currentThread().getName() + ": 跑完全程");
        }
    }
}

执行结果:

2,Barrier

2.1 Barrier的概念

  barrier是一个集合点,也是一个同步辅助类,它允许多个线程在某一个点上进行同步。具体的实现类是CyclicBarrier。

  • CyclicBarrier
    • 构造函数是需要同步的线程数量
    • await 等待其他线程,到达预定的数量后,就放行。

2.2 Barrier的使用

  假定有三行数,用三个线程分别计算每一行数的和,最终计算总和。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) throws InterruptedException {
        final int[][] numbers = new int[3][];
        final int[] result = new int[3];
        numbers[0] = new int[]{1,2,3,4,5,6,7,8,9};
        numbers[1] = new int[]{10,11,12,13,14,15};
        numbers[2] = new int[]{16,17,18,19,20};

        CalculateFinalResult finalResultCalculator = new CalculateFinalResult(result);
        CyclicBarrier barrier = new CyclicBarrier(3, finalResultCalculator);
        // 当有3个线程在barrier上await时,就自动执行回调动作:finalResultCalculator

        for (int i = 0; i < 3; i++) {
            CalculateEachRow rowCalculator = new CalculateEachRow(barrier, numbers, result, i);
            new Thread(rowCalculator).start();
        }
    }
}

class CalculateEachRow implements Runnable {
    final CyclicBarrier barrier;
    final int[][] numbers;
    final int[] result;
    final int rowNumber;

    public CalculateEachRow(CyclicBarrier barrier, int[][] numbers, int[] result, int rowNumber) {
        this.barrier = barrier;
        this.numbers = numbers;
        this.result = result;
        this.rowNumber = rowNumber;
    }

    @Override
    public void run() {
        for (int data : numbers[rowNumber]) {
            result[rowNumber] += data;
        }

        try {
            System.out.println(Thread.currentThread().getName() +
                    ": 计算第 " + (rowNumber + 1) +" 行结束,结果是: " + result[rowNumber]);
            /*
            * 当在Barrier上await的线程数量达到预定的要求后,
            * 所有的await的线程不在等待 全部解锁
            * 并且 Barrier将自动执行预定的回调动作
            * 本程序中回调动作是CalculateFinalResult*/
            barrier.await(); // 等待,只要有3个(Barrier的构造参数) 就放行
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

class CalculateFinalResult implements Runnable {
    final int[] eachRowRes;
    int finalRes;

    public CalculateFinalResult(int[] eachRowRes) {
        this.eachRowRes = eachRowRes;
    }

    @Override
    public void run() {
        finalRes = 0;
        for (int data : eachRowRes) {
            finalRes += data;
        }
        System.out.println("最终的结果为:" + finalRes);
    }
}

执行结果:

3,Phaser

3.1 Phaser的概念

  phaser也是一个同步辅助类,它允许执行并发多阶段的任务,在每个阶段结束的位置对线程进行同步,当所有线程都达到这步,再进行下一步。可以多次来应用。

  主要类:Phaser,在Phaser类中有一系列的arrive方法,这里主要使用arriveAndAwaitAdvance()方法。

3.2 Phaser的用法

  假设举行考试,总共三道大题,每次下发一道题目,等所有学生完成后再进行下一道。

package com.antique;

import java.util.concurrent.Phaser;

public class PhaserExample {
    public static void main(String[] args) throws InterruptedException {
        int STU_CNT = 5;
        Phaser phaser = new Phaser(STU_CNT);

        for (int i = 0; i < STU_CNT; i++) {
            new Thread(new Student(phaser)).start();
        }
    }
}

class Student implements Runnable {
    private final Phaser phaser;

    public Student(Phaser phaser) {
        this.phaser = phaser;
    }

    @Override
    public void run() {
        try {
            doTesting(1);
            phaser.arriveAndAwaitAdvance(); // 停下来等待其他线程完成
            doTesting(2);
            phaser.arriveAndAwaitAdvance();
            doTesting(3);
            phaser.arriveAndAwaitAdvance();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void doTesting(int i) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + ": 开始答题目 " + i);
        long workingTime = (long) (Math.random() * 2000);
        Thread.sleep(workingTime);
        System.out.println(Thread.currentThread().getName() + ": 完成了题目 " + i);
    }
}

执行结果:

  从运行结果中可以看出,只有当所有线程都完成了题目1时,才能开始答题目2,这就实现了在某个阶段结束的位置对线程进行同步。

4,Exchanger

4.1 Exchanger的概念

  Exchanger并不是用来做线程协作控制的,它是用来在线程当中互相交换信息,两个线程之间相互发消息。前面我们学过线程之间要达到信息交流,只有通过共享变量才能够实现。

  Exchanger允许在2个线程中定义同步点,当两个线程都达到同步点时,它们交换数据结构。

  Exchanger里面的主要方法就是exchange(),用于线程双方互相交换数据,交换数据是双向的

4.2 Exchanger的使用

  本例通过Exchanger实现学生成绩的查询,实现简单的线程间数据的交换。

package com.antique.lock;

import java.util.Scanner;
import java.util.concurrent.Exchanger;

public class ExchangerExample {
    public static void main(String[] args) throws InterruptedException {
        // 定义交换的数据类型
        Exchanger<String> exchanger = new Exchanger<>();
        // 定义一个后台工作者线程
        BackgroundWorker worker = new BackgroundWorker(exchanger);
        new Thread(worker).start();

        Scanner in = new Scanner(System.in);
        while (true) {
            System.out.println("输入要查询的学生姓名:");
            String input = in.nextLine().trim();
            /*
            * 当两个线程都执行到同一个exchanger的exchange方法,
            * 两个线程就相互交换数据,交换是双向的
            * 24行的exchange和47行的exchange对应
            * 对于main线程来说 它把input这个值交换给了item
            * 对于后台线程来说 它把null这个值交换给了main线程 main线程觉得没有 就没有接受*/
            exchanger.exchange(input);  // 把用户数据传递给后台线程
            String value = exchanger.exchange(null);   // 拿到后台线程反馈结果
            if ("exit".equals(value)) {
                break;
            }
            System.out.println("查询结果:" + value);
        }
        in.close();
    }
}

class BackgroundWorker implements Runnable {
    final Exchanger<String> exchanger;

    public BackgroundWorker(Exchanger<String> exchanger) {
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        boolean flag = true;
        while (flag) {
            try {
                String item = exchanger.exchange(null);
                switch (item) {
                    case "zhangsan":
                        exchanger.exchange("90");
                        break;
                    case "lisi":
                        exchanger.exchange("80");
                        break;
                    case "wangwu":
                        exchanger.exchange("70");
                        break;
                    case "exit":
                        exchanger.exchange("exit");
                        flag = false;
                        break;
                    default:
                        exchanger.exchange("查无此人");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果:

  需要注意的是只有当两个线程都运行到exchange() 方法时,才会进行数据交换,并且数据交换时双向的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值