Javaマルチスレッドと高い同時実行性との戦い-JUC

この記事は、Javaマルチスレッドと高い同時実行性の知識を学ぶときに作成されたメモです。

この部分には多くのコンテンツがあり、内容に応じて5つの部分に分かれています。

  1. マルチスレッドの基本
  2. JUCの記事
  3. 同期コンテナと同時コンテナ
  4. スレッドプール
  5. MQの記事

この記事はJUCの記事です。

目次

1 JUCとは何ですか?

2並行性と並列性

3Callableを使用してスレッドを作成します

4ロック

4.1ロック

4.2フェアロックとアンフェアロック

4.3錠の生産者と消費者の問題

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

5つのスレッドと8つのロック

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

6.1 CountDownLatch

6.2CyclicBarrier

6.3セマフォ


1 JUCとは何ですか?

java.util.concurrent、Java同時実行ツールキット。主に含まれています:

  • java.util.concurrent
  • java.util.concurrent.atomic
  • java.util.concurrent.locks

 

2並行性と並列性

同時実行性:複数のスレッドが実行をすばやくローテーションするため、巨視的なビューで複数のスレッドが同時に実行される効果があります。

並列:マルチコアCPUでは、複数のスレッドが同時に実行されます。

 

3Callableを使用してスレッドを作成します

スレッドを作成するために、呼び出し可能なインターフェースがjava.util.concurrentで提供されています。

Runnableインターフェースによって作成されたスレッドと比較すると、Callableインターフェースによって作成されたスレッドは次のとおりです。

  • 戻り値を持つことができます
  • 例外をスローできます

コードデモ:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
 
public class CreateThread {
    static class MyCallable implements Callable<Integer> { //泛型规定返回值类型
        @Override
        public Integer call() throws Exception { //重写call()方法,类似于Runnable接口中的run()方法
            System.out.println("Hello MyCallable");
            return 1024;
        }
    }
 
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask futureTask = new FutureTask(myCallable); //适配器模式
        new Thread(futureTask).start(); //调用start()方法启动线程
        //打印返回值
        Integer result = (Integer) futureTask.get();
        System.out.println(result);
    }
}

Integer result =(Integer)futureTask.get();このコード行はスレッドブロッキングを引き起こす可能性があることに注意してください。

 

4ロック

4.1ロック

synchronizedキーワードの使用に加えて、JUCは別のスレッド同期メソッドであるLockを提供します。

Lockは、java.util.concurrent.locksパッケージのインターフェースであり、次の3つの実装クラスがあります。

  • ReentrantLock、リエントラントロック
  • ReentrantReadWriteLock.ReadLock、再入可能読み取りロック
  • ReentrantReadWriteLock.WriteLock、リエントラント書き込みロック

ロックを使用する3つのステップ:

  1. ロックロック=新しいReentrantLock()
  2. lock.lock()
  3. lock.unlock()、ロックリソースを解放するためにunlock()メソッドが呼び出されない場合、デッドロックが発生します。これは通常、finallyコードブロックに配置されます。

コードデモ:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {
    public static void main(String[] args) {
        MyLock myLock = new MyLock();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                myLock.test();
            }).start();
        }
    }
}

class MyLock {
    private int number = 10;
    Lock lock = new ReentrantLock();

    public void test() {
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + " 操作后,number = " + --number);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

演算結果:

スレッド0操作後、番号= 9
スレッド3操作、番号= 8
スレッド4操作後、番号= 7
スレッド5操作後、番号= 6
スレッド7操作後、番号= 5
スレッド1操作、番号= 4
スレッド2操作、番号= 3
スレッド6操作、番号= 2
スレッド8操作、番号= 1
スレッド9操作、番号= 0

4.2フェアロックとアンフェアロック

公正なロック:複数のスレッドは、ロックを適用する順序でロックを取得し、新しいスレッドは直接待機キューに入り、キューに入れられます。

不公平なロック:新しいスレッドはロックを取得しようとし、取得できない場合は待機キューに入ります。

同期ロックは非フェアロックです。ロックロックはデフォルトで非フェアロックであり、フェアロックも使用できます。

ロックはフェアロックを使用します:

ロックロック= new ReentrantLock(true); 

4.3錠の生産者と消費者の問題

同期ロックは、Objectクラスのモニターメソッド(wait、notify、notifyAll)を使用してスレッド間の通信を実装し、同様のモニターメソッドがjava.util.concurrent.locksパッケージで提供されます。

ロックモニターを使用する手順:

  1. ロックロック=新しいReentrantLock()
  2. 条件condition = lock.newCondition()
  3. lock.lock()
  4. condition.await()、スレッド待機は、常にループに表示されます。
  5. condition.signalAll()、他のスレッドに通知します。
  6. lock.unlock()は通常、finallyコードブロックで記述されます。

生産者と消費者の問題:

複数のスレッドが同時に同じ変数番号を操作します。

  • プロデューサーが操作するたびに、number ++
  • 消費者が操作するたびに、数-

数値== 0の場合、消費者はそれを操作できません。

コードデモ:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        int n = 4; //消费者数目
        int k = 5; //每个消费者的消费额
        new Thread(() -> {
            for (int i = 0; i < n * k; i++) {
                data.increment();
            }
        }, "Producer").start(); //生产者
        for (int id = 0; id < n; id++) {
            new Thread(() -> {
                for (int i = 0; i < k; i++) {
                    data.decrement();
                }
            }, "Consumer" + id).start(); //消费者们
        }
    }
}

class Data {
    private int number = 0;
    Lock lock = new ReentrantLock(); //创建锁
    Condition condition = lock.newCondition(); //创建监视器

    public void increment() {
        try {
            lock.lock();
            while (number > 3) {
                condition.await(); //线程等待
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            condition.signalAll(); //通知其它线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decrement() {
        try {
            lock.lock();
            while (number <= 0) {
                condition.await(); //线程等待
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            condition.signalAll(); //通知其它线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

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

読み取り/書き込みロックReadWriteLockは、基本的にきめ細かいロックロックであり、読み取りロックと書き込みロックに分けられます。

  • 読み取りロック:共有ロックとも呼ばれ、同時に複数のスレッドで占有できます。読み取り専用操作に使用されます。
  • 書き込みロック:排他ロック、排他ロックとも呼ばれ、1つのスレッドのみが占有できます。書き込み操作に使用されます。

複数のスレッドが同じリソースを読み書きする場合:

  • 複数のスレッドが同時に読み取り操作を実行できるようにする
  • 複数のスレッドが同時に書き込み操作を実行することは許可されていません
  • 読み取り操作と書き込み操作を同時に実行しないでください

上記の要件は、読み取り/書き込みロックを使用することで満たすことができます。

読み取りロックを使用する手順:

  • ReadWriteLock readWriteLock = new ReentrantReadWriteLock()
  • readWriteLock.readLock()。lock()
  • readWriteLock.readLock()。unlock()、通常はfinallyコードブロックで記述されます。

書き込みロックを使用する手順:

  • ReadWriteLock readWriteLock = new ReentrantReadWriteLock()
  • readWriteLock.writeLock()。lock()
  • readWriteLock.writeLock()。unlock()は通常、finallyコードブロックに書き込まれます。

コードデモ:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * ReadWriteLock
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.write(temp + "", temp);
            }).start();
        }
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "读取结果:" + myCache.read(temp + ""));
            }).start();
        }
    }
}

/**
 * 自定义缓存
 */
class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void write(String key, Object value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入完成");
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public Object read(String key) {
        Object o;
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取");
            o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取完成");
        } finally {
            readWriteLock.readLock().unlock();
        }
        return o;
    }
}

演算結果:

スレッド0書き込み
スレッド0書き込み完了
スレッド2書き込み
スレッド2書き込み完了
スレッド1書き込み
スレッド1書き込み完了
スレッド3書き込み
スレッド3書き込み完了
スレッド4書き込み
スレッド4書き込み完全
スレッド5読み取り
スレッド-5読み取り完了
スレッド-9読み取り
スレッド-9読み取り完了
スレッド-5読み取り結果:0
スレッド-6読み取り
スレッド-6読み取り完了
スレッド-8読み取り取得
スレッド-8読み取り完了
スレッド-9読み取り結果:。4
スレッド-7読み取り
スレッド7読み取り完了
スレッド7読み取り結果:2
スレッド8読み取り結果:。3
スレッド読み取り結果。6:1

 

5つのスレッドと8つのロック

8つのスレッドロックは8つの実際のロックではなく、ロックに関する8つの古典的な問題です。

最初の質問:次のコードでは、操作Aまたは操作Bのどちらが最初に実行されますか?

import java.util.concurrent.TimeUnit;

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        Client client = new Client();
        new Thread(() -> {
            client.actionA();
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            client.actionB();
        }).start();
    }
}

class Client {
    public synchronized void actionA() {
        System.out.println("执行操作A");
    }

    public synchronized void actionB() {
        System.out.println("执行操作B");
    }
}

回答:操作A。間違いない。

2番目の質問:次のコードでは、操作Aまたは操作Bを最初に実行する必要がありますか?

import java.util.concurrent.TimeUnit;

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        Client client = new Client();
        new Thread(() -> {
            client.actionA();
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            client.actionB();
        }).start();
    }
}

class Client {
    public synchronized void actionA() {
        try {
            TimeUnit.SECONDS.sleep(2); //线程阻塞
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行操作A");
    }

    public synchronized void actionB() {
        System.out.println("执行操作B");
    }
}

回答:操作A。スレッドAは2秒間ブロックされますが、2秒前にすでにロックリソースを取得しています。

3番目の質問:次のコードでは、操作A、操作B、または操作Cを最初に実行する必要がありますか?

import java.util.concurrent.TimeUnit;

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Client client = new Client();
        new Thread(() -> {
            client.actionA();
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            client.actionB();
        }).start();
        new Thread(() -> {
            client.actionC();
        }).start();
    }
}

class Client {
    //同步方法
    public synchronized void actionA() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行操作A");
    }
    //同步方法
    public synchronized void actionB() {
        System.out.println("执行操作B");
    }
    //普通方法
    public void actionC() {
        System.out.println("执行操作C");
    }
}

回答:最初にCを操作し、次にAを操作し、最後にBを操作します。通常の方法は、ロックリソースを取得せずに実行できます。

4番目の質問:次のコードでは、操作Aまたは操作Bのどちらが最初に実行されますか?

import java.util.concurrent.TimeUnit;

public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        Client client1 = new Client();
        Client client2 = new Client();
        new Thread(() -> {
            client1.actionA(); //client1调用
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            client2.actionB(); //client2调用
        }).start();
    }
}

class Client {
    public synchronized void actionA() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行操作A");
    }

    public synchronized void actionB() {
        System.out.println("执行操作B");
    }
}

回答:操作B。スレッドロックのターゲットはオブジェクトであり、異なるオブジェクト間でロックの競合は発生しません。

5番目の質問:次のコードでは、操作Aまたは操作Bのどちらが最初に実行されますか?

import java.util.concurrent.TimeUnit;

public class Test5 {
    public static void main(String[] args) throws InterruptedException {
        Client client = new Client();
        new Thread(() -> {
            client.actionA();
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            client.actionB();
        }).start();
    }
}

class Client {
    //静态同步方法
    public static synchronized void actionA() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行操作A");
    }
    //静态同步方法
    public static synchronized void actionB() {
        System.out.println("执行操作B");
    }
}

回答:操作A。注意すべきことは、静的メソッドによってロックされたオブジェクトはClient.classであるということです。

6番目の質問:次のコードでは、操作Aまたは操作Bのどちらが最初に実行されますか?

import java.util.concurrent.TimeUnit;

public class Test6 {
    public static void main(String[] args) throws InterruptedException {
        Client client1 = new Client();
        Client client2 = new Client();
        new Thread(() -> {
            client1.actionA(); //client1调用
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            client2.actionB(); client2调用
        }).start();
    }
}

class Client {
    //静态同步方法
    public static synchronized void actionA() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行操作A");
    }
    //静态同步方法
    public static synchronized void actionB() {
        System.out.println("执行操作B");
    }
}

回答:操作A。静的メソッドによってロックされるオブジェクトはClient.classです。

7番目の質問:次のコードでは、操作Aまたは操作Bのどちらが最初に実行されますか?

import java.util.concurrent.TimeUnit;

public class Test7 {
    public static void main(String[] args) throws InterruptedException {
        Client client = new Client();
        new Thread(() -> {
            client.actionA();
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            client.actionB();
        }).start();
    }
}

class Client {
    //静态同步方法
    public static synchronized void actionA() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行操作A");
    }
    //同步方法
    public synchronized void actionB() {
        System.out.println("执行操作B");
    }
}

回答:操作B。間違いない。

8番目の質問:次のコードでは、操作Aまたは操作Bのどちらが最初に実行されますか?

import java.util.concurrent.TimeUnit;

public class Test8 {
    public static void main(String[] args) throws InterruptedException {
        Client client1 = new Client();
        Client client2 = new Client();
        new Thread(() -> {
            client1.actionA(); //client1调用
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            client2.actionB(); //client2调用
        }).start();
    }
}

class Client {
    //静态同步方法
    public static synchronized void actionA() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行操作A");
    }
    //同步方法
    public synchronized void actionB() {
        System.out.println("执行操作B");
    }
}

回答:操作B。間違いない。

 

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

6.1 CountDownLatch

CountDownLatchは、ユニバーサル同期ツールです。その中心的なアイデアは次のとおりです。

1つ以上のスレッドが、他のスレッドでの一連の操作が完了するまで待機できるようにします。

CountDownLatchクラスの主要なメソッドは次のとおりです。

  • countDown()、countDown()が呼び出されるたびに、CountDownLatchカウンターの値は-1です。
  • await()、現在のスレッドは、CountDownLatchカウンターがゼロにリセットされるまで待機し、現在のスレッドをウェイクアップします。

コードデモ:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        int count = 5;
        CountDownLatch countDownLatch = new CountDownLatch(count); //设置计数器countDownLatch的初始值

        for (int i = 0; i < count; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName());
                countDownLatch.countDown(); //countDownLatch的值-1
            }).start();
        }
        countDownLatch.await();//等待countDownLatch归零,再往下执行

        System.out.println("ok");
    }
}

6.2CyclicBarrier

CyclicBarrierは、ユニバーサル同期ツールでもあります。その中心的なアイデアは次のとおりです。

スレッドのグループがすべて、互いに共通のバリアポイントに到達するのを待つことを許可します。

CyclicBarrierクラスの主要なメソッドは次のとおりです。

  • 待つ()

コードデモ:

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

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        int count = 7;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(count, () -> { //设置计数器cyclicBarrier的阈值
            System.out.println("召唤神龙成功");
        });

        for (int i = 0; i < count; i++) {
            //lambda表达式不能直接操作 i
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集到" + (temp + 1) + "星球");
                try {
                    cyclicBarrier.await(); //cyclicBarrier的值+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "继续工作");
            }).start();
        }
    }
}

演算結果:

スレッド-0収集1惑星
スレッド-4収集5惑星
スレッド-2収集3惑星
スレッド-3収集4惑星
スレッド-1収集2惑星
スレッド-5収集6惑星
スレッド-6収集7惑星
呼び出しドラゴン成功
スレッド-6継続
Thread-0は動作し続けます
Thread-4は動作し続けます
Thread-3は動作し続けます
Thread-2は
動作し続けますThread-5は動作し続けます
Thread-1は動作し続けます

6.3セマフォ

セマフォ、セマフォは通常、スレッドの数を制限するために使用されます。

セマフォは一連のライセンスを維持します。必要に応じて、各スレッドはライセンスが取得されるまでブロックされます。

セマフォクラスの主要なメソッドは次のとおりです。

  • Acquisition()の場合、現在のセマフォは-1です。現在のセマフォが0の場合は、待機します。
  • release()の場合、現在のセマフォは+1です。

コードデモ:

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        int count = 2;
        Semaphore semaphore = new Semaphore(count);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "得到许可,开始了它的表演");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "表演结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }).start();
        }
    }
}

演算結果:

Thread-0はパーミッションを取得し、パフォーマンスを開始しました
Thread-1はパーミッションを取得し、パフォーマンスを開始しました
Thread-1のパフォーマンスは終了しました
Thread-0のパフォーマンスは終了しました
Thread-2はパーミッションを取得し、パフォーマンスを開始しました
Thread-3はパーミッションを取得し、パフォーマンスを開始しました
Thread-2のパフォーマンス終了
スレッド-3パフォーマンスが終了
スレッド-4許可が、それはその公演の始め
に終了スレッド-4の性能を
 

ビデオリンクの学習:(これはとても良いです)

https://www.bilibili.com/video/BV1B7411L7tE

加油!(d•_•)d

おすすめ

転載: blog.csdn.net/qq_42082161/article/details/113868802