JUC 同時プログラミング (1) Thread、TreadPoolExecutor、BlockingQueue、Synchronized、Lock、JUC 補助クラス

記事ディレクトリ

マルチスレッドの作成方法

スレッドの継承

  1. 方法 1: Thread クラスを継承し、ステップを作成します。
    • Thread を継承するサブクラス MyThread を定義し、run() メソッドを書き換えます。
    • MyThreadクラスのオブジェクトを作成する
    • スレッドオブジェクトのstart()メソッドを呼び出してスレッドを開始します(起動後にrun()メソッドが実行されます)

この作成メソッドは既に Thread クラスを継承しているため、他のクラスを継承できず、拡張には役立ちません。

  1. 方法 2: Runnable インターフェイスを実装するクラスを宣言します。
    • 定義 Runnable インターフェイスを実装し、run() メソッドをオーバーライドするために、スレッド タスク クラス MyRunnable を定義します。
    • MyRunnable オブジェクトを作成する
    • MyRunnable オブジェクトを処理のために Thread に渡します
    • Thread オブジェクトの start メソッドを呼び出して開始します。

実行可能なインターフェースを実装する

2 番目のメソッドはインターフェイスのみを実装します。クラスを継承してインターフェイスを実装し続けることができ、スケーラビリティが強化されます。ただし、欠点は、プログラミング用のパッケージング層が余分に存在することです (スレッド オブジェクトを構築するには、実行可能オブジェクトをスレッドに渡す必要があります)。
ここに画像の説明を挿入
方法 1 の実装:

public class MyThread extends Thread{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 5; ++i) {
    
    
            System.out.println("子线程执行输出:" + i);
        }
    }
}
 Thread t1 = new MyThread();
 t1.start();

方法 2 の実装:

Thread t = new Thread(new Runnable() {
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(i);
        }
    }
});
for (int i = 0; i < 10; i++) {
    
    
    System.out.println("主线程:" + i);
}

もちろん、ラムダ式を省略表現として使用することもできます。

 Thread t = new Thread(() -> {
    
    
     for (int i = 0; i < 10; i++) {
    
    
         System.out.println( "子线程" + i);
     }
 });
 for (int i = 0; i < 10; i++) {
    
    
     System.out.println("主线程:" + i);
 }
  1. 方法 3
    最初の 2 つの作成方法には問題があります。
    • 書き換えられた run() メソッドはいずれも結果を直接返すことができません
    • スレッドの実行結果を返す必要があるビジネス シナリオには適していません。

Callable インターフェースを実装するクラスを定義し、FutureTask にアセンブルします。

jdk5 は、Callable インターフェイスと FutureTask インターフェイスを使用して上記の機能を実現します。

ステップを作成します。

  • Callable インターフェースを実装するクラスを定義し、call メソッドを書き換えて、実行する内容をカプセル化します。
  • FutureTask を使用して、Callable オブジェクトをスレッド タスク オブジェクトにカプセル化します。
  • スレッド タスク オブジェクトを処理のために Thread に渡します。
  • Thread の start メソッドを呼び出してスレッドを開始し、タスクを実行します。
  • スレッドの実行後、FutureTask の get() メソッドを使用してタスクの実行結果を取得します。

方法 3 の実装:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo3 {
    
    
    public static void main(String[] args) {
    
    
        Callable<String> call1 = new MyCallable(100000);
        FutureTask<String> f1 = new FutureTask<>(call1);
        Thread t1 = new Thread(f1);
        t1.start();

        Callable<String> call2 = new MyCallable(100000);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
    
    
            // 如果f1没有执行完毕,那么get这里会等待,直至完成
            String r1 =  f1.get();
            System.out.println("第一个结果:" + r1);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
            // 如果f2没有执行完毕,那么get这里会等待,直至完成
        try {
    
    
            String r2 =  f2.get();
            System.out.println("第二个结果:" + r2);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String> {
    
    
    private int n;
    public MyCallable (int n) {
    
    
        this.n = n;
    }
    @Override
    public String call() throws Exception {
    
    
        int sum = 0;
        for (int i = 0; i <= n; i++) {
    
    
            sum += i;
        }
        return "子线程执行的结果是:" + sum;
    }
}

共通API

ここに画像の説明を挿入
ここに画像の説明を挿入

ネイティブAPI

寝る

スレッドの sleep メソッドが呼び出されると、システム タイマーとスケジューラの精度に応じて、現在実行中のスレッドが指定されたミリ秒間スリープ (実行を一時的に停止) します。いずれかのスレッドが現在のスレッドに割り込むと、現在のスレッドの割り込みステータスをクリアするために InterruptedException がスローされます。

割り込み

  • スレッドがinterrupt()を呼び出すとき、スレッドが通常のアクティブ状態にある場合、スレッドの割り込みフラグはtrueに設定されます。それ以上のことはありません。割り込みフラグが設定されたスレッドは影響を受けずに通常どおり実行を続けます。つまり、interrupt() は割り込みフラグを設定するだけで、実際にはスレッドに割り込みを行わず、呼び出されたスレッドの協力を必要とします。人に「黙れ」と言っているようなものですが、結局その人が黙るかどうかはその人の協力次第です。
  • スレッドがブロック状態 (スリープ、待機、結合など) にある場合、別のスレッドで現在のスレッド オブジェクトの中断 () メソッドを呼び出すと、スレッドは即座にブロック状態を終了し、割り込みステータスが解除されます。フラグはクリアされ、InterruptedException 例外がスローされます。
  • 非アクティブなスレッドの場合、interrupt() を呼び出しても効果はありません。

参加する

join メソッドでは、あるスレッドが別のスレッドに参加する前に実行できます。このスレッドの実行中、他のスレッドはブロッキング状態になります。もちろん、join 入力パラメーター (実行待ちのタイムアウト時間を指定) を指定して待機することもできます。スレッドが終了するまでの時間は最大でも数ミリ秒です。タイムアウトが 0 の場合は、永久に待機することを意味します。

public class demo1 {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(2);
                System.out.println("+++++++++++++");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
        t1.start();
        try {
    
    
            t1.join();
            System.out.println("ok");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

収率

降伏の文字通りの意味は屈服することです。このメソッドを呼び出すと、現在のスレッドがプロセッサの現在の使用を放棄する意思があることがスケジューラに示され、スケジューラはこれを自由に無視できます。

yield は、CPU を過剰に使用してしまうスレッド間の相対的な進行状況を改善するためのヒューリスティックな試みです。yield メソッドを使用する場合、通常は 2 つの使用シナリオがあります。

  • 実際に望ましい効果があることを確認するには、収量の使用を詳細な分析とベンチマークと組み合わせる必要がありますが、このアプローチはほとんど使用されません。デバッグやテストの目的に役立つ場合があり、競合状態によるバグの再現に役立つ場合があります。
  • java.util.concurrent.locks パッケージのような同時実行制御構造を設計するときにも役立つ場合があります。
public class TestYield {
    
    
    public static void main(String[] args) {
    
    
        MyThread thread1 = new MyThread("thread-1");
        MyThread thread2 = new MyThread("thread-2");
        thread1.start();
        thread2.start();
    }
 
    private static class MyThread extends Thread {
    
    
        public MyThread(String name) {
    
    
            super(name);
        }
        @Override
        public void run() {
    
    
            for (int i = 1; i <= 5; i++) {
    
    
                if (i % 2 == 0) {
    
    
                    Thread.yield();
                    System.out.println(getName() + ":" + i);
                }
            }
        }
    }
}

トレッドプールエグゼキュータ

スレッドプールはスレッドを再利用できる技術です。新しいスレッドを作成するオーバーヘッドは非常に高いため、スレッド プールを使用するとスレッドを再利用でき、プログラムのパフォーマンスが向上します。
ここに画像の説明を挿入

スレッドプールオブジェクトを取得する

JDK5.0 以降、スレッド プールを表すインターフェイスが提供されています。
ExecutorService はどのようにしてスレッド プール オブジェクトを取得しますか?

  • 方法 1: ExecutorService の実装クラス ThreadPoolExecutor を使用して、スレッド プール オブジェクトを自己作成するこの方法は最も柔軟です。

  • 方法 2: Executor (スレッド プールのツール クラス) を使用してメソッドを呼び出し、さまざまな特性を持つスレッド プール オブジェクトを返します。

エグゼキュータの組み込みスレッド プール

1. newCachedThreadPool は
キャッシュ可能なスレッド プールを作成します。スレッド プールの長さが処理の必要性を超えた場合、アイドル状態のスレッドを柔軟にリサイクルできます。リサイクル可能なスレッドがない場合は、新しいスレッドが作成されます。このタイプのスレッド プールの特徴は、
作成されるワーカー スレッドの数にほとんど制限がないため (実際には制限があり、数は Integer. MAX_VALUE です)、スレッド プールにスレッドを柔軟に追加できることです。 。

スレッド プールにタスクが長期間送信されなかった場合、つまり、ワーカー スレッドが指定された時間 (デフォルトでは 1 分) アイドル状態になった場合、ワーカー スレッドは自動的に終了します。終了後に新しいタスクを送信すると、スレッド プールはワーカー スレッドを再作成します。CachedThreadPool を使用する場合は、タスク数の制御に注意する必要があります。そうしないと、同時に多数のスレッドが実行されるため、システム OOM が発生する可能性があります。

2. newFixedThreadPool は、
指定された数のワーカー スレッドを含むスレッド プールを作成します。タスクが送信されるたびにワーカー スレッドが作成され、ワー​​カー スレッドの数がスレッド プールの初期最大数に達すると、送信されたタスクはプール キューに格納されます。

FixedThreadPool は典型的な優れたスレッド プールであり、プログラムの効率を向上させ、スレッド作成のオーバーヘッドを節約するという利点があります。ただし、スレッド プールがアイドル状態のとき、つまりスレッド プールに実行可能なタスクがないときは、ワーカー スレッドは解放されず、特定のシステム リソースも占有します。

3. newSingleThreadExecutor は
シングルスレッド Executor を作成します。つまり、タスクを実行するための一意のワーカー スレッドのみを作成し、タスクの実行には唯一のワーカー スレッドのみを使用して、すべてのタスクが指定された順序 (FIFO、LIFO、優先)。このスレッドが異常終了すると、別のスレッドが代わりに実行され、順次実行が保証されます。シングル ワーカー スレッドの最大の特徴は、タスクが順番に実行されることが保証され、同時に複数のスレッドがアクティブになることがないことです。
4. newScheduleThreadPool は
、固定長のスレッド プールを作成し、タイミングと定期的なタスクの実行をサポートし、タイミングと定期的なタスクの実行をサポートします。

スレッドプールエグゼキュータ

ここに画像の説明を挿入
一時スレッドはいつ作成されますか?

  • 新しいタスクが送信されると、コア スレッドはビジー状態になり、タスク キューもいっぱいになり、一時スレッドも作成される可能性があり、その場合にのみ一時スレッドが作成されます。
    いつタスクを拒否し始めますか?
  • コアスレッドと一時スレッドの両方がビジー状態で、タスクキューもいっぱいで、新しいタスクが来るとタスクは拒否されます。

ここに画像の説明を挿入

スレッド プールは実行可能なタスクを処理します

        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        Runnable target = () -> {
    
    
            try {
    
    
                Thread.sleep(100000);
                System.out.println(Thread.currentThread().getName() + "输出");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        };
        // 三个核心线程
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 五个在等待队列
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 等待队列满了,新加两个临时线程
        pool.execute(target);
        pool.execute(target);
        
        // 拒绝任务,抛出异常
        pool.execute(target);

    }

スレッド プールは呼び出し可能なタスクを処理します

ここに画像の説明を挿入
実行は実行可能なタスクを実行し、送信は呼び出し可能なタスクを処理します。

BlockingQueue ブロッキングキュー

BlockingQueue は FIFO (先入れ先出し) キューで、マルチスレッドでデータを効率的かつ安全に「送信」する方法の問題を解決します。
ここに画像の説明を挿入
ブロッキング キューには、要素を追加および削除するための 4 つの API があります。

  • ノンブロッキング、ブール値を返します
    • 例外がスローされます: add()、remove()、element()
    • 例外はスローされません: offer()、poll()、peek()
  • ブロック
    • 常にブロックされる: put()、take()
    • 待機時間、タイムアウト時のリターンを設定できます: Offer(e, timeout,unit)、poll(timeout,unit)

ここに画像の説明を挿入

LinkedBlockingQueue リンク リスト ブロッキング キュー

二重リンクリストのブロッキングキュー。

FixedThreadPool と SingleThreadExector の場合、使用するブロッキング キューは Integer.MAX_VALUE の容量を持つ LinkedBlockingQueue であり、無制限のキューと見なすことができます。

FixedThreadPool スレッドプールのスレッド数は固定であるため
、タスクを処理するために特に多くのスレッドを追加する方法はなく、このときタスクを格納するには LinkedBlockingQueue などの容量制限のないブロッキングキューが必要です。

ここで注意すべき点は、スレッド プールのタスク キューが満杯になることはなく、スレッド プールはコア スレッドの数だけスレッドを作成するため、現時点でのスレッドの最大数はスレッド プールにとって意味がありません。コア スレッドの数に基づく複数のスレッドの生成はトリガーされません。

SynchronousQueue 同期キュー

SynchronousQueue 同期キューには要素が格納されず、要素が入れられる限り、要素を取り出して取得する必要があります。

対応するスレッド プールは CachedThreadPool です。スレッドプール CachedThreadPool の最大スレッド数は Integer の最大値であり、無限にスレッド数を拡張できることがわかります。
CachedThreadPool は、前のスレッド プールであるFixedThreadPoolの反対です。FixedThreadPoolの場合、ブロッキング キューの容量は無制限です。ここで、CachedThreadPoolのスレッド数は無限に拡張できるため、CachedThreadPoolスレッド プールは必要ありません。タスクを保存するためのタスク キュー。タスクが送信されると、タスクは直接スレッドに転送されるか、個別に保存せずに実行する新しいスレッドが作成されます。

SynchronousQueue<String> bq = new SynchronousQueue();
new Thread(() -> {
    
    
    try {
    
    
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
}, "A").start();

new Thread(() -> {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(2);
        bq.take();
        System.out.println(Thread.currentThread().getName());
        bq.take();
        System.out.println(Thread.currentThread().getName());
        bq.take();
        System.out.println(Thread.currentThread().getName());
    } catch (Exception e){
    
    
        e.printStackTrace();
    }
}, "B").start();
}

DelayedWorkQueue 遅延ブロックキュー

3 番目のブロッキング キューは DelayedWorkQueue です。対応するスレッド プールは ScheduledThreadPool と SingleThreadScheduledExecutor です。これら 2 つのスレッド プールの最大の特徴は、一定時間後にタスクを実行したり、一定の間隔でタスクを実行したりするなど、タスクの実行を遅延させることができることです。 . .

DelayedWorkQueueの特徴は、内部要素が投入された時間でソートされるのではなく、遅延の長さに応じてタスクがソートされ、内部的には「ヒープ」なデータ構造を利用していることです。スレッド プール ScheduledThreadPool と SingleThreadScheduledExecutor が DelayedWorkQueue を選択する理由は、スレッド プール自体が時間に基づいてタスクを実行し、遅延キューはタスクの実行を容易にするために時間でタスクを並べ替えるだけであるためです。

同期済み

1、并发就是多线程操作同一个资源。

2、在Java中,线程就是一个单独的资源类,没有任何附属操作。资源中包含并发操作的属性、方法。

同時実行のケース - マルチスレッドのチケット購入の例:
この例では、パブリック リソース クラスは Ticket、left はチケットの残りの数、cnt は販売されたチケットの記録された数、そして sale() メソッドは誰が購入したかを出力します。チケットが余っている場合 チケットを1枚とし、現在の残りチケットと販売済みチケットの合計を表示します。

class Ticket {
    
    
    int left;
    int cnt = 0;
    public int getLeft() {
    
    
        return left;
    }

    public void setLeft(int left) {
    
    
        this.left = left;
    }

    public Ticket(int n) {
    
    
        this.left = n;
    }
    public void sale() {
    
    
        try {
    
    
            Thread.sleep(100); // 模拟卖票耗时
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        if (left > 0) {
    
    
            ++cnt;
            --left;
            System.out.println("线程:" + Thread.currentThread().getName() + "剩余:" + left + ",共卖出:" + cnt);
        }
    }
}

main 関数で、投票数、チケットを購入する人の数、各人がチケットを購入する試行回数を設定します。

public static void main(String[] args) {
    
    
    int ticketNum = 8, people = 4, chance = 5;
    Ticket t = new Ticket(ticketNum);
    System.out.println("开售前:---------" + t.getLeft());
    for (int i = 0; i < people; ++i) {
    
    
        new Thread(() -> {
    
    
            for (int j = 0; j < chance; ++j) {
    
    
                t.sale();
            }
        }, Integer.toString(i)).start();
    }
    try {
    
    
        Thread.sleep(3000);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
}

結果:
ここに画像の説明を挿入

同期されたソリューション

同期の同期実装の基礎: Java のすべてのオブジェクトをロックとして使用できます。具体的には、次の 3 つの形式を取ります。
1. 对于普通同步方法,锁是当前实例对象。
2. 对于静态同步方法,锁是当前类的Class对象。
3. 对于同步方法块,锁是Synchonized括号里配置的对象。

sale() を直接同期メソッドにします。つまり、各スレッドが sale() メソッドにアクセスすると、現在のメソッドが配置されているインスタンス (つまり、パブリック リソース) のオブジェクトをロックし、スレッドは 1 つだけになります。は同時に動作できますが、他のスレッドは待機する必要があります。
ここに画像の説明を挿入
ここに画像の説明を挿入

ロックロック

ロックには 3 つの手順が必要です。

  1. ロックを作成します。 Lock lk = new ReentrantLock();
  2. ロックを取得します: lk.lock
  3. try - catch -finally では、同時実行制御を必要とするビジネス ロジックを try に記述し、例外が発生したときにロックが正常に解放されることを保証するために、finally でロックが解放されます。
class Ticket2 {
    
    
    int left;
    int cnt = 0;
    Lock lk = new ReentrantLock();
    public int getLeft() {
    
    
        return left;
    }

    public void setLeft(int left) {
    
    
        this.left = left;
    }

    public Ticket2(int left) {
    
    
        this.left = left;
    }

    public void sale() {
    
    
        lk.lock();
        try {
    
    
            if (left > 0) {
    
    
                ++cnt;
                --left;
                System.out.println("线程:" + Thread.currentThread().getName() + "剩余:" + left + ",共卖出:" + cnt);
            }
        }
        catch (Exception e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            lk.unlock();
        }
    }
}

ReentrantLock は同時実行の安全性も保証します。
ここに画像の説明を挿入

シンクロロックとロックロックの違い

  1. Synchronized は Java 組み込みキーワードであり、Lock は Java クラスです。
  2. Synchronized はロックの状態を取得できませんが、Lock はロックが取得されたかどうかを判断できます。
  3. 同期すると自動的にロックが解除されますが、手動でロックする必要があり、解除しないとデッドロックが発生します。
  4. 同期すると、ロックを取得していないスレッドは永久に待機します ロック ロックは、ロックを取得しようとする仕組みがあり、必ずしも永久に待機するわけではありません。
  5. synchronized は再入可能で不公平です。Lock lock は再入可能で公平なロックを設定できます。つまり、1 つは変更できない組み込みキーワードで、もう 1 つはカスタマイズされます。
  6. 同期は少量のコード同期の問題をロックするのに適しており、ロックは大量の同期コードをロックするのに適しています。

生産者消費者問題

同期された実装

スレッド A と B は同じ変数 num を操作し、A は num + 1、
B は num - 1 を実行し、この 2 つは交互に使用されます。

ここで、スレッド同期を実現するには、A が操作を完了した後、B に通知する必要があり、操作が完了した後、B に通知する必要があります。これは、A が生成された後、消費のために A を B に引き渡すことと同じです。そして、B が消費を完了した後に A に通知します。

この生産消費モデルのプログラミング 3 部作を完了してください。

  • 待機: 条件が満たされない場合、while ループは待機します。
  • 業務:条件が満たされた場合に業務を実行します。
  • 通知: 用事が完了したら、他のスレッドに通知します。

メンバー変数 num を持つリソース クラス Data を構築し、+1 を実行する同期メソッドと -1 を実行する同期メソッドを 2 つ構築し、メイン メソッドで 2 つのスレッド A と B を起動し、+1 と - を操作してみます。それぞれ 1:

class Data {
    
    
    private int num = 0;

    public synchronized void increase() {
    
    
        while (num != 0) {
    
    
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        ++num;
        System.out.println(Thread.currentThread().getName() +":->"+ num);
        this.notifyAll();
    }

    public synchronized void decrease() {
    
    
        while (num != 1) {
    
    
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        --num;
        System.out.println(Thread.currentThread().getName() +":->"+ num);
        this.notifyAll();
    }
}

    public static void main(String[] args) {
    
    
        Data d = new Data();
        new Thread(() -> {
    
    
            for (int i = 0; i < 5; i++) {
    
    
                d.increase();
            }
        }, "A").start();

        new Thread(() -> {
    
    
            for (int i = 0; i < 5; i++) {
    
    
                d.decrease();
            }
        }, "B").start();
    }
}

ロックバージョンの実装 - 条件

ここに画像の説明を挿入
ロックを使用して条件条件変数を構築できます。条件には、wait()、notify()、notifyAll と同様の、await() メソッド、signal() および signalAll() メソッドが用意されています。

主な手順:

  • 1. ReentrantLock() lk を作成し、lk の条件を取得します。
  • 2. ロック: lk.lock()
  • 3、トライ~キャッチ~ファイナル
    • try にビジネス ロジックを記述します。
      • 待機: 条件が満たされない場合、while ループは待機します。
      • 業務:条件が満たされた場合に業務を実行します。
      • 通知: ビジネスが完了したら、他のスレッドに通知します。
    • 最後にロックを解放します: lk.unlock();
class Data2 {
    
    
    private int num = 0;
    Lock lk = new ReentrantLock();
    Condition condition = lk.newCondition();
    
    public  void increase() {
    
    
        lk.lock();
        try {
    
    
            while (num != 0) condition.await();
            ++num;
            System.out.println(Thread.currentThread().getName() +":->"+ num);
            condition.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lk.unlock();
        }
    }

    public  void decrease() {
    
    
        lk.lock();
        try {
    
    
            while (num != 1) condition.await();
            --num;
            System.out.println(Thread.currentThread().getName() +":->"+ num);
            condition.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lk.unlock();
        }
    }
}

正確な通知ウェイクアップを実現する条件

ここに画像の説明を挿入

呼び出し可能

結果を取得するための戻り値を伴う非同期リクエストに使用されます。
最初のステップは、独自の Callable オブジェクトを構築し、Callable インターフェイスを実装することです。これには、予期される結果の型を識別するための汎用パラメーターが必要です。

class  MyCall implements Callable<Integer> {
    
    
    int a, b;

    public MyCall(int a, int b) {
    
    
        this.a = a;
        this.b = b;
    }

    @Override
    public Integer call() throws Exception {
    
    
        TimeUnit.SECONDS.sleep(2);
        return a + b;
    }
}

Callable オブジェクトは FutureTask とともにパッケージ化されます。つまり、将来実行されるタスクとしてパッケージ化されます。

MyCall call = new MyCall(3, 4);
FutureTask future = new FutureTask(call);

ここに画像の説明を挿入
RunnableFuture 複合インターフェイスは FutureTask に実装されています。つまり、Runnable の実装があるため、Thead に入れて開始できます。

        new Thread(future).start();
        Integer a = 0;
        try {
    
    
            a = (Integer) future.get();
        } catch (InterruptedException | ExecutionException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(a.toString());

この future.get() はブロックします。

JUC で一般的に使用される補助クラス

CountDownLatch (カウントダウンタイマー)

CountDownLatch を使用すると、すべてのスレッドのタスクが実行されるまで、count スレッドを 1 か所でブロックできます。

教室に 6 人の生徒がいて、生徒全員が退出した後でないとドアを閉めることができないシーンをシミュレートします。

public static void main(String[] args) {
    
    
    // 1、统计num个线程的倒计时器
    int num = 6;
    CountDownLatch cn = new CountDownLatch(num);
    for (int i = 0; i < num; ++i) {
    
    
        new Thread(()->{
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "离开");
            // 2、线程结束前,倒数一下
            cn.countDown();
        }, String.valueOf(i)).start();
    }
    try {
    
    
        // 3、等待所有线程结束
        cn.await();
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    System.out.println("关门!");
}

ここに画像の説明を挿入

CyclicBarrier(サイクリックフェンス)

CyclicBarrier は CountDownLatch に非常に似ています。スレッド間の技術的な待機も実装できます。これが行う必要があるのは、スレッドのグループがバリア (同期ポイントとも呼ばれます) に到達したときに、最後のスレッドがバリアに到達するまでブロックすることです。ドアが開くと、バリアによってブロックされたすべてのスレッドが引き続き動作します。

現在のシナリオを逆にして、教師が指定された数に達する前に到着する人の数がドアを開けることを許可すると仮定します。

ステップ 1: CyclicBarrier を作成し、満たされるスレッドの数と、すべてのスレッドが到着した後に実行される実行可能オブジェクトを指定します。

CyclicBarrier cb = new CyclicBarrier(num, () -> {
    
    
   System.out.println("开门!");
});

ステップ 2: 各スレッドの実行が終了する前に、 を使用してcb.await();他のスレッドが同期するのを待ちます。

public static void main(String[] args) {
    
    
    // 1、等待的人数到达num后,才开门!
    int num = 6;
    CyclicBarrier cb = new CyclicBarrier(num, () -> {
    
    
        System.out.println("开门!");
    });
    for (int i = 0; i < num; ++i) {
    
    
        new Thread(()->{
    
    
            System.out.println(Thread.currentThread().getName() + "到达");
            try {
    
    
                // 2、线程结束前,需等待其他线程同步
                cb.await();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }, String.valueOf(i)).start();
    }
}

ここに画像の説明を挿入

セマフォ セマフォ - 複数のスレッドが同時にアクセスできるようにします。

synchronized と ReentrantLock の両方で、一度に 1 つのスレッドのみがリソースにアクセスできます。Semaphore(信号量)可以指定多个线程同时访问某个资源。

セマフォには、フェア モードとアンフェア モードの 2 つのモードがあります。

  • Fair モード:acquire が呼び出される順序は、FIFO に続いてライセンスが取得される順序です。
  • アンフェアモード: プリエンプティブ

施工方法:

public Semaphore(int permits) {
    
    
        sync = new NonfairSync(permits);
    }
public Semaphore(int permits, boolean fair) {
    
    
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

これら 2 つの構築方法はどちらも許可数を指定する必要があります。2 番目の構築方法では、公平モードか不公平モードかを指定でき、デフォルトは不公平モードです。

ここに画像の説明を挿入

最も一般的に使用されるシナリオは、駐車スペースを確保するシーンのシミュレーションなど、リソースが制限されている場合に、指定された数のスレッドのみが特定のリソースに同時にアクセスできるようにすることです。

ステップ 1:
リソース状況をシミュレートします: num 個の駐車スペース、ユーザーは 10 個の
int num = 3、total = 6;
Semaphore semaphore = new Semaphore(num);

ステップ 2:
try -catch -final:
try: semaphore.acquire(); // リソースを取得する
finally: semaphore.release(); // リソースを解放する

public static void main(String[] args) {
    
    
    // 1、num个车位,而用户有total 个
    int num = 3, total = 6;
    Semaphore sm = new Semaphore(num);
    for (int i = 0; i < total; ++i) {
    
    
        new Thread(()->{
    
    
            try {
    
    
                sm.acquire();
                System.out.println(Thread.currentThread().getName() + "抢到车位");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "离开车位");
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                sm.release();
            }
        }, String.valueOf(i)).start();
    }
}

駐車スペースを同時に利用できるのは 3 名までです。
ここに画像の説明を挿入

ReadWriteLock 読み取り/書き込みロック

読み取り/書き込みロックを使用してカスタム キャッシュを実装し、書き込み時に許可される操作は 1 つだけです。

ステップ 1: 読み取り/書き込みロックを定義します:
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
ステップ 2: 読み取り操作関数を定義します:
読み取り前に読み取りロックを追加します
readWriteLock.readLock().lock();
読み取り後にロックを解放します
ステップ3:
書き込み時に書き込みロックを追加し、書き込み後にロックを解除する書き込み操作関数 () を定義します。

class MyCache {
    
    
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Map<String, Object> mp = new HashMap<>();

    public void put(String s, Object o) {
    
    
        readWriteLock.writeLock().lock();
        try {
    
    
            mp.put(s, o);
            System.out.println(Thread.currentThread().getName() + "插入:" + s);
        } catch (Exception exception) {
    
    
            exception.printStackTrace();
        } finally {
    
    
            readWriteLock.writeLock().unlock();
        }
    }

    public Object get(String s) {
    
    
        Object ans = null;
        readWriteLock.readLock().lock();
        try {
    
    
            ans = mp.get(s);
            System.out.println(Thread.currentThread().getName() + "查询:" + s);
        } catch (Exception exception) {
    
    
            exception.printStackTrace();
        } finally {
    
    
            readWriteLock.readLock().unlock();
        }
        return ans;
    }
}

4つの主要な機能インターフェース

関数型インターフェイスは、抽象メソッドを 1 つだけ定義するインターフェイス、または @FunctionalInterface アノテーションが付けられたインターフェイスです。デフォルトのメソッドが存在する可能性があります。
4 つの機能インターフェイスは次のとおりです。

  • 関数関数インターフェイス (Function): 1 つのインターフェイス入力は関数の入力であり、もう 1 つは関数の出力です。
  • コンシューマ関数インターフェイス (Consumer): インターフェイス入力は関数の入力であり、関数の戻り値はブール値です。
  • 供給機能インターフェース (サプライヤー)
  • 述語関数インターフェイス (述語)

機能/機能的

機能インターフェイス: 1 つのインターフェイス入力は関数の入力であり、もう 1 つは関数の出力です。

@FunctionalInterface
public interface Function<T, R> {
    
    

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
    // ... 两个默认函数和一个静态函数
}

アサーションタイプ

決定インターフェイス: インターフェイスの入力は関数の入力関数であり、関数の戻り値はブール値です。

public interface Predicate<T> {
    
    

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
    // ...三个默认函数和一个静态函数
}

消費タイプ

コンシューマ インターフェイス: 入力のみで出力なし

@FunctionalInterface
public interface Consumer<T> {
    
    

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
    // ...一个默认方法
}

供給タイプ

電源インターフェース: 出力のみ、入力なし

@FunctionalInterface
public interface Supplier<T> {
    
    

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
        Consumer consumer = (str) -> {
    
    
            System.out.println(str);
        };
        consumer.accept("Happy");
    }

使用例 - ストリームストリーミングプログラミング

    /**
    有5个用户,筛选
     1、ID 必须是偶数
     2、年龄必须大于23
     3、用户名转大写字母
     4、倒序排序
     5、只需要一个用户
     **/
    public static void main(String[] args) {
    
    
        List<User> list = new ArrayList<>();
        Collections.addAll(list,
                new User(0, 22, "lzy"),
                new User(1, 20, "blzy"),
                new User(2, 25, "azy"),
                new User(3, 24, "czy"),
                new User(4, 24, "dzy"),
                new User(5, 24, "ezy"),
                new User(6, 24, "fzy"),
                new User(7, 24, "gsy"));
        list.stream().filter(e -> {
    
    return e.getId() % 2 == 1;})
                     .filter(e -> {
    
    return e.getAge() > 23;})
                     .map(e -> {
    
    return e.getUsername().toUpperCase();})
                     .sorted(Comparator.reverseOrder())
                     .limit(1)
                     .forEach(System.out::println);

    }

おすすめ

転載: blog.csdn.net/baiduwaimai/article/details/132083149