マルチスレッドケース

1.シングルトンモード

シングルトン モードは、プログラム内に特定のクラスのインスタンスが 1 つだけ存在することを保証でき、複数のインスタンスが作成されることはありません. シングルトン モードの具体的な実装方法は、「ハングリーマン」と「レイジーマン」に分けられます.

1.1 ハングリーマンモード

ここに画像の説明を挿入
クラスのロード段階では、インスタンスが作成されます。これはハングリーマン モードと呼ばれます。

1) static は、このインスタンスが一意であることを保証します
2) static は、このインスタンスが特定の時間に作成されることを保証します

//此处保证Singleton这个类只能创建出一个实例
class Singleton{
    
    
    //在此处,先把这个实例给创建出来
    private static Singleton instance = new Singleton();
    //如果需要使用这个唯一实例,统一通过Singleton.getInstance()方式来获取
    public static Singleton getInstance(){
    
    
        return instance;
    }
    //为了避免Singleton类不小心被复制出多份
    //把构造方法设为private.在类外面,就无法通过new的方式来创建这个Singleton实例
    private Singleton(){
    
    

    }
}

public class Thread20 {
    
    
    public static void main(String[] args) {
    
    
        Singleton s = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s == s2);
    }

}

結果: 真

1.2 レイジーモード

ここに画像の説明を挿入上記のハングリーマンモードとレイジーマンモードはマルチスレッド環境で getInstance を呼び出しており、ハングリーマンモードはスレッドセーフであるのに対し、レイジーマンモードはセーフではないことがわかります。

ハングリー モードのマルチスレッド呼び出し、「読み取り操作」のみを含み、スレッドは安全

怠け者モードの実行プロセスは
ここに画像の説明を挿入
この時点でロックされます
ここに画像の説明を挿入

ただし、getInstance をロックする必要があるたび
に、オブジェクトが新しくなり、getInstance を呼び出すと、この時点でインスタンスの値が空でない必要があるため、return が直接トリガーされます。

上記の議論に基づいて、上記のコードに判定を追加できます. オブジェクトが作成されていない場合はロックされます.
オブジェクトが作成されている場合はロックされません.
コードは次のとおりです
ここに画像の説明を挿入
:コードにはまだ問題があります
ここに画像の説明を挿入

instance=new Singleton() は
3 つのステップに分割されます

  1. メモリースペースを申し込む
  2. コンストラクターを呼び出して、このメモリ空間を適切なオブジェクトに初期化します
  3. インスタンス参照にはメモリ空間のアドレスを割り当てます
    . 通常は 123 の順番で実行されます.
    コンパイラも演算を最適化し, 命令の並び替えを行います
    . プログラムの効率を上げるために, コードの実行順序を調整します
    . 123 は 132 になる可能性があります (シングルスレッドの場合、123 と 132 の間に本質的な違いはありません)


132の手順に従ってt1が実行されたと仮定すると、
t1が13を実行した後、2を実行する前に、実行するCPU t2から切り出されます
(t1が3を実行した後、t2はここでの参照が空ではないように見えます)
この時点で、 t2 は、インスタンス参照を直接返すことと同等であり
、参照内の属性を使用しようとする場合があります

ただし、t1 の 2 (読み込み) 操作が実行されていないため、t2 は不正なオブジェクト、つまり
volatile を必要とする構築されていない不完全なオブジェクトを取得します。

1. メモリの可視性を解決する
2. 命令の並べ替えを無効にする

完全なコードは次のとおりです。

class SingletonLazy{
    
    
    private volatile static SingletonLazy instance = null;
    public static SingletonLazy getInstance(){
    
    
        if (instance == null) {
    
    
            synchronized (SingletonLazy.class) {
    
    
                if (instance == null) {
    
    
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){
    
    

    }
}
public class Thread21 {
    
    
    public static void main(String[] args) {
    
    
        SingletonLazy s = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s == s2);
    }

2.ブロッキングキュー

ブロッキング キューは、先入れ先出しのキューですが、ブロッキング
1 の特別な機能を持つ特別なキューです。キューが空の場合、キュー操作が実行されるとブロックされ、別のスレッドをブロックして要素を追加します(キューが空でなくなるまで)
2. キューがいっぱいになると、別のスレッドがキューから要素を取得するまで (キューがいっぱいでなくなるまで)、エンキュー操作もブロックされます
。キューは「生産者 - 消費者モデル」です

2.1 生産者消費者モデル

典型的なシナリオを見てみましょう
ここに画像の説明を挿入

上記のシナリオでは、A と B の間のカップリングは比較的高い
. A が B に電話をかけたい場合、A は B の存在を知っている必要があります. B が電話を切ると、A にバグが発生しやすくなります. C サーバーが追加され、A も多くのコードを変更するには

上記のシナリオでは、プロデューサー/コンシューマー モデルを使用することで結合を効果的に減らすことができます。
ここに画像の説明を挿入

この時点で、A と B の間の結合度は大幅に減少します.
A は B の存在を知らず、A はキューのみを知っています (A のコードには B に関連するコード行はありません).
Bは A の存在を知らず、B はキューのみを知っています (B A に関連するコード行はありません)

B がハングしても、A には何の影響もありません。キューがまだ正常であるため、A は引き続き要素をキューに挿入できます。キューがいっぱいの場合、同じ理由でブロックされます。A がハングし、B は影響を受けず、B は影響を受けません
。キューから要素を取得し、キューが空になるまで待ち、最初にブロックする
生産者消費者モデルの利点:
1. 送信者と受信者の間の「分離」を実現する
2. 「山を切り、谷を埋める」ことができる"、システムの安定性を確保する

2.2 標準ライブラリが提供するブロッキングキュー

最初にインターフェイスを理解しましょう:
ここに画像の説明を挿入
実装クラス:ここに画像の説明を挿入
ブロッキング キューの主な方法は 2 つあります

1. エンキュー、プット
2. デキュー、テイク
オール ブロッキング機能あり

関連コード

public class Thread23 {
    
    
    public static void main(String[] args) {
    
    
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();
        //创建两个线程,来作为生产者和消费者
        Thread customer = new Thread(()->{
    
    
            while (true) {
    
    
                try {
    
    
                    Integer result = blockingQueue.take();
                    System.out.println("消费元素: " + result);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }

            }
        });
        customer.start();
        Thread producer = new Thread(()->{
    
    
            int count = 0;
            while (true){
    
    
                try {
    
    
                    blockingQueue.put(count);
                    System.out.println("生产元素 : "+ count);
                    count++;
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
            }
        });
        producer.start();

    }
}

2.3 ブロッキング キューの実装

2.3.1 通常キュー

class MyBlockingQueue{
    
    
    private int[] items = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;
    public void put(int value){
    
    
        if(size == items.length){
    
    
            return;
        }
        items[tail] = value;
        tail++;
        //1)
//        tail = tail % items.length;
        //2)
        if(tail >= items.length){
    
    
            tail = 0;
        }
        size++;
    }
    public Integer take(){
    
    
        if(size == 0){
    
    
            return null;
        }
        int result = items[head];
        head++;
        if(head >= items.length){
    
    
            head = 0;
        }
        size --;
        return result;
    }
}

キューが空か満杯かを判断する
1) 要素を無駄にする
2) サイズ、レコード数を導入する

2.3.2 ブロッキング機能の追加

class MyBlockingQueue{
    
    
    private int[] items = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;
//    入队列
    public void put(int value) throws InterruptedException {
    
    
        synchronized (this) {
    
    
            while (size == items.length){
    
    
                //队列满了,此时要产生阻塞
                this.wait();
            }
            items[tail] = value;
            tail++;
            //tail的处理
            //tail = tail % items.length;
            if(tail >= items.length){
    
    
                tail = 0;
            }
            size++;
//            这个notify唤醒take中的wait
            this.notify();
        }

    }
    //出队列
    public Integer take() throws InterruptedException {
    
    
        int result = 0;
        synchronized (this) {
    
    
            while (size == 0){
    
    
                //队列空,也应该阻塞
                this.wait();
            }
            result = items[head];
            head++;
            if(head >= items.length){
    
    
                head = 0;
            }
            size--;
            //唤醒put中的wait
            this.notify();
        }
        return result;
    }

}

ここに画像の説明を挿入

2 つのスレッドでの待機を同時にトリガーすることはできません

ここに画像の説明を挿入

待機時間は同じです

3. タイマー

「目覚まし時計」と同様に、設定された時間になると指定されたコードが実行されます.
ネットワークをプログラミングするときは、タイマーを使用して「ロスを止める」ことができます.

3.1 標準ライブラリのタイマー

注釈ライブラリにはタイマー クラスが用意されています. タイマー クラスのコア メソッドはスケジュールです. スケジュール メソッドには2 つのパラメーターが含まれています. 最初のパラメーターはタスク コードを実行する時間であり, 2 番目のパラメーターは特定の
タスクを実行する時間を指定します.
ここに引用があります
コード

public class Thread25 {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("程序启动!");
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("运行定时器任务1");
            }
        },3000);
        timer.schedule(new TimerTask() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("运行定时器任务2");
            }
        },2000);
        timer.schedule(new TimerTask() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("运行定时器任务3");
            }
        },1000);
    }
}

結果

ここに引用があります

おすすめ

転載: blog.csdn.net/weixin_63993025/article/details/129799144