Java マルチスレッド 06 - JUC 同時実行パッケージ 02

1 スレッド同期ツール​CountDownLatch​クラス

​CountDownLatch​他のスレッドで実行されている一連の操作が完了するまで 1 つ以上のスレッドを待機できるようにする同期

  • クラス​CountDownLatch​は同期カウンタです。構築中に int パラメータが渡されます。このパラメータはカウンタの初期値です。​countDown()​メソッドが呼び出されるたび1 ずつデクリメントされます。 0 より大きい場合、​await()​メソッドは現在のスレッドの続行をブロックします。
  • ​countDown()​メソッドが呼び出されるため、​await()​メソッドは現在のカウントがゼロになるまでブロックされます。その後、待機中のスレッドはすべて解放され、​await()​メソッドすぐに戻りますが、この現象は 1 回だけ発生し、カウントはリセットされません。1 つまたは複数のスレッドは、別の N 個のスレッドが何かを実行する前に完了するのを待ちます。

スレッドクラスの作成

import java.util.concurrent.CountDownLatch;

public class UserThread1 extends Thread {
    private int sum1 = 0;
    private CountDownLatch cd;

    public UserThread1(CountDownLatch cd){
        this.cd = cd;
    }

    @Override
    public void run() {
        for(int i=0;i<=50;i++){
            sum1 += i;
        }
        cd.countDown();
    }

    public int getSum1(){
        return sum1;
    }
}
import java.util.concurrent.CountDownLatch;

public class UserThread2 extends Thread {
    private int sum2 = 0;
    private CountDownLatch cd;

    public UserThread2(CountDownLatch cd){
        this.cd = cd;
    }

    @Override
    public void run() {
        for(int i=51;i<=100;i++){
            sum2 += i;
        }
        cd.countDown();
    }

    public int getSum2(){
        return sum2;
    }
}
 
 

​countDown()​このメソッドはスレッド クラスでカウンターを更新するために使用されます。

テストクラスを作成する

import java.util.concurrent.CountDownLatch;

public class Test {
    public static void main(String[] args) {
        CountDownLatch cd = new CountDownLatch(2);
        UserThread1 thread1 = new UserThread1(cd);
        UserThread2 thread2 = new UserThread2(cd);
        thread1.start();
        thread2.start();

        try {
            cd.await();
            int sum = thread1.getSum1() + thread2.getSum2();
            System.out.println("1~100 的和是:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
 
 

出力は次のとおりです。

1~100の合計は5050となります。

await() メソッドはテスト クラスで呼び出され、CountDownLatch が初期化されるときのカウンターが 0 になるまで現在のスレッドをブロックします。

知らせ:

  • CountDownLatch の初期値は、すべてのスレッドの countDown() の数と同じである必要があります。
  • 上記のテスト クラスが 1 に初期化され、初期値が countDown() の数より小さい場合、つまりプログラム​CountDownLatch cd = new CountDownLatch(1);​の実行出力結果は次のようになります。

1~100の合計は1275となります。

  • 上記のテスト クラスが 3 に初期化され、初期値が countDown() の数より大きい場合、つまりプログラム​CountDownLatch cd = new CountDownLatch(3);​の実行プログラムは一時停止され、ブロッキング状態から抜け出すことができません。

2 スレッド同期ツール​CyclicBarrier​クラス

​CyclicBarrier​は、共通のバリア ポイントに到達するまでスレッドのグループが相互に待機できるようにする同期ヘルパー クラスです 待ち合わせ場所に似ています。

バリアは待機中のスレッドが解放された後も再利用できるため、循環バリアと呼ばれます。

2.1 スレッドによって呼び出されるリソース クラスの作成

public class TimeCount {
    private int count1;
    private int count2;
    private int sum;

    public int getCount1() {
        return count1;
    }

    public void setCount1(int count1) {
        this.count1 = count1;
    }

    public int getCount2() {
        return count2;
    }

    public void setCount2(int count2) {
        this.count2 = count2;
    }

    public int getSum() {
        return this.count1 + this.count2;
    }

    public void setSum(int sum) {
        this.sum = sum;
    }
}

2.2 スレッドクラスの作成

タスクの実行が完了したら、 を使用してブロッキング ポイント​cyclicBarrier.await();​を追加します。

import java.util.concurrent.CyclicBarrier;

public class UserRunn implements Runnable{
    private TimeCount tc;
    private String name;
    private CyclicBarrier cyclicBarrier;

    public UserRunn(TimeCount tc, String name, CyclicBarrier cyclicBarrier){
        this.tc = tc;
        this.name = name;
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        if(name.equals("看书")){
            try {
                Thread.sleep(4000);
                tc.setCount1(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        else if(name.equals("写字")){
            try {
                Thread.sleep(2000);
                tc.setCount2(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //阻塞点,等所有线程运行完毕,自动解锁
        try {
            cyclicBarrier.await();
            System.out.println("------------" + name + " end-----------");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

2.3 テストクラスの作成

CyclicBarrier を初期化します。ブロッキング ポイントに同時に到達するには 2 つのスレッドが必要なので、初期化の​new CyclicBarrier(2, new Runnable())​最初の​parties​2 に設定されます。

import java.util.concurrent.CyclicBarrier;

public class Test {
    public static void main(String[] args) {
        TimeCount tc = new TimeCount();

        CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
            @Override
            public void run() {
                int sum = tc.getSum();
                System.out.println("功能总耗时:" + sum);
            }
        });
        new Thread(new UserRunn(tc, "看书", cyclicBarrier)).start();
        new Thread(new UserRunn(tc, "写字", cyclicBarrier)).start();
    }
}
 
 

2.4 出力結果

合計機能時間: 6000

---------------読み終わり----------

---------------書き終わり----------

「読み取り」スレッドは 4 秒間実行する必要があり、「書き込み」スレッドは 2 秒間実行する必要があります。ブロッキング ポイントに到達すると、実行時間の短いスレッドは、実行時間の長いスレッドを待機します。セクション 2.3 でブロッキング ポイントに達するスレッドの数が作成されると、CyclicBarrier オブジェクトがパーティの値で初期化されると、すべてのスレッドが同時に解放されます。

知らせ:

作成された CyclicBarrier オブジェクトは、バリア ポイントに到達するまでに達成したいスレッドの数と同じパーティ値で初期化する必要があります。

パーティが期待値より小さい場合、収束効果は達成されません。

パーティの数が予想より多い場合、スレッドはブロックされたままになります。

3 スレッド同期ツール​Semaphore​クラス

これはカウンティング セマフォであり​Semaphore​、その本質は共有ロックであり、AQS に基づいて実装され、状態変数を通じて共有されます。
acquire メソッドを呼び出すと状態値が 1 減算され、release メソッドを呼び出すと状態値が 1 増加します。
状態変数が 0 未満の場合、ブロックされ、AQS キューで待機します。

3.1 レストランクラスの作成

オブジェクトを宣言する

レストラン クラスで、​Semaphore​セマフォ​new Semaphore(num)​

ロック

電流制限が必要な操作の前にロックします​acquire();​。この位置に入るスレッドの数が許容最大値に達すると、他のスレッドは待機状態になります。

ロックを解除する

電流制限操作が完了すると、ロックが解放され​release();​、後続のスレッドが​acquire();​ステートメントに入ります。

import java.util.concurrent.Semaphore;

public class Restaurant {
    //通过引入Semaphore,实现餐厅限流
    private final Semaphore semaphore;

    public Restaurant(int num){
        //设置最大可用的并行信号量
        this.semaphore = new Semaphore(num);
    }

    public void eatConsumers(){
        try {
            //加锁
            semaphore.acquire();

            System.out.println(Thread.currentThread().getName() + "进入餐厅");

            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "离开餐厅");
            //释放锁
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
 
 

3.2 コンシューマクラスの作成

public class Consumer extends Thread {
    private Restaurant restaurant;

    public Consumer(Restaurant restaurant){
        this.restaurant = restaurant;
    }

    @Override
    public void run() {
        restaurant.eatConsumers();
    }
}

3.3 テストクラスの作成

電流制限の最大スレッド数を 2 に設定します。

public class Test {
    public static void main(String[] args) {
        Restaurant restaurant = new Restaurant(2);

        for(int i=0;i<5;i++) {
            new Consumer(restaurant).start();
        }
    }
}

3.4 出力結果

Thread-1 がレストランに入る

Thread-4 がレストランに入る

スレッド 1 がレストランを出る

Thread-4 がレストランを出る

Thread-2 がレストランに入る

Thread-3 がレストランに入る

スレッド 2 がレストランを出る

Thread-0 がレストランに入る

スレッド 3 がレストランを出る

Thread-0 がレストランを出る

4 スレッド交換​Exchanger​クラス

Exchanger はスレッド間の連携に使用されるツール クラスであり、スレッド間のデータ交換に使用されます。
これは、2 つのスレッドが互いのデータを交換できる同期ポイントを提供します。
2 つのスレッドは、exchange() メソッドを通じてデータを交換します。

最初のスレッドが最初にexchange() メソッドを実行すると、2番目のスレッドもexchange() メソッドを実行するのを待ちます。両方のスレッドが同期ポイントに到達すると、2 つのスレッドはデータを交換し、このスレッドからデータを生成できます。送信データは相手に渡されます。

import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
    private static final Exchanger<String> exchanger = new Exchanger<>();
    private static ExecutorService executorService = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                String a = " A 银行转入";
                System.out.println(Thread.currentThread().getName() + a);
                try {
                    String b = exchanger.exchange(a);
                    System.out.println(Thread.currentThread().getName() + b);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                String a = " B 银行转出";
                System.out.println(Thread.currentThread().getName() + a);
                try {
                    String b = exchanger.exchange(a);
                    System.out.println(Thread.currentThread().getName() + b);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        executorService.shutdown();
    }
}
 
 

実行出力

pool-1-thread-1 銀行振込

pool-1-thread-2 B 銀行送金アウト

pool-1-thread-2 銀行振込

pool-1-thread-1 B 銀行送金アウト

注意しなければならないことは次のとおりです。

データ交換に参加するスレッドの数が偶数の場合、ペアごとのデータ交換を実現できます。

データ交換に参加するスレッドの数が奇数の場合、プログラムは常にブロックされます。

5 ​Fork​-糸の​Join​仕組み

Fork/Joinフレームワークは、JAVA7が提供するタスクを並列実行するためのフレームワークで、
大きなタスクをいくつかの小さなタスクに分割し、最後にそれぞれの小さなタスクの結果をまとめて大きなタスクの結果を得るフレームワークです。

分割統治:

大規模な問題を小規模なサブ問題に分割し、次にそれらを分割して征服し、最後にサブ問題の解決策を組み合わせて元の問題の解決策を取得します。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class ContTask extends RecursiveTask<Integer> {
    private int start;
    private int end;

    //计算任务量的阈值,最小任务量范围
    private static final int TASKSIZE = 30;
    private static int count = 0;

    public ContTask(int start, int end){
        this.start = start;
        this.end = end;
    }

    //重写compute方法,任务执行的主要计算
    @Override
    protected Integer compute() {
        int sum = 0;

        System.out.println("开启线程进行计算" + count++);
        boolean state = (end - start) <= TASKSIZE;
        //如果小于等于任务的阈值
        if(state){
            //无需拆分任务计算
            for(int i=start;i<=end;i++){
                sum += i;
            }
        }else{
            //进行拆分任务计算
            System.out.printf("start-%d, end-%d这个任务需要进行拆分任务计算。。。%s%n", start, end, Thread.currentThread().getName());
            //分割成两个任务
            int middle = (end + start) / 2;
            ContTask contTask1 = new ContTask(start, middle);
            ContTask contTask2 = new ContTask(middle+1, end);
            //开启线程计算分布式任务
            invokeAll(contTask1, contTask2);
            //阻塞,直到任务完成或取消
            Integer tasksum1 = contTask1.join();
            Integer tasksum2 = contTask2.join();
            //结果合并
            sum = tasksum1 + tasksum2;
        }

        return sum;
    }

    public static void main(String[] args) {
        //分布式计算池
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        //初始化设置任务
        ContTask contTask = new ContTask(1, 100);
        //分布式计算任务,提交任务
        ForkJoinTask forkJoinTask = forkJoinPool.submit(contTask);
        //得到最终计算结果
        try {
            System.out.println("最终计算结果为:" + forkJoinTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

 
 

結果:

計算0のスレッドを開始します

タスク開始-1、終了-100はタスク計算に分割する必要があります。ForkJoinPool-1-worker-1

計算1のスレッドを開始します

タスク開始-1、終了-50はタスク計算に分割する必要があります。ForkJoinPool-1-worker-1

計算2のスレッドを開始します

タスク開始 51 と終了 100 はタスク計算に分割する必要があります。フォーク結合プール-1-ワーカー-2

計算4のスレッドを開始します

計算3のスレッドを開始します

計算6のスレッドを開始します

計算5のスレッドを開始します

最終的な計算結果は次のようになります: 5050

おすすめ

転載: blog.csdn.net/QQ156881887/article/details/128987036