【マルチスレッドアドバンス】ラップを歌ったり踊ったり、バスケットボールをしたりする順番を確実にする方法

ここに画像の説明を挿入

序文
アバ、アバアバアバアバアバ、アバ、アバアバ

あなたはそれを学ぶことができないのではないかと心配しているので、私はこのデモを思い付くためにもう少し髪を費やしました


最近そんな要望があります(実シーン)

10分に1回、同時にユーザーにメッセージを送信するために10個のスレッドを開きました。そのうち、9個のビジネススレッドがメッセージの送信を担当し、残りの1個は最新のメッセージ戦略を取得するために使用されます(いわゆる戦略は送信するユーザーを指定するために、送信するチャネル、WeChat、電子メール、SMSなどを介して)、各取得の理由は、戦略がいつでも変更される可能性があるためです。私は、最新のものを取得するたびに戦略を変更するように設計しました。 、プロジェクトを再開せずに戦略を柔軟に変更できます(戦略はデータベースの中央にあります)

ここに問題があります

各ビジネススレッドは最新の戦略を使用してメッセージを送信する必要があるため、雑用スレッドを最初に実行する必要があり、スレッドのスケジュールはランダムであり、実行順序はオペレーティングシステムによって決定されます。サービススレッドを後で実行するにはどうすればよいですか。コーラススレッドは終了しましたか?

これには、スレッド間の通信が含まれます。この栗の目的は、実際のプロジェクトでのスレッド間の通信の適用について、すべての人に一般的な理解を与えることです。

このシーンは本物ですが、理解していなくても構いません

私のことをよく知っている友達は、ブロガーが温かい男性であることを知っています。もちろん、彼らが持っている栗はそれほど退屈ではありません。

それで

今日の主人公は:Ah Ji

ここに画像の説明を挿入
すみません、鶏肉、これをしなければなりません

ストーリーの背景:Ah Jiは、最初にラップを歌って踊ることを学び、次にバスケットボールをすることを学ぶ必要があります

マルチスレッドに変換:一方のスレッドは歌と踊りのラップの学習を担当し、もう一方のスレッドはバスケットボールのプレイの学習を担当します。これら2つのスレッドのスケジュールはランダムであり、順序はオペレーティングシステムによって決定されます。 Ah Jiがラップを歌ったり踊ったりすることを学び、次にバスケットボールをすることを学ぶようにします

多くの方法がありますが、ここでは、すべてのシナリオを処理するのに十分な、一般的に使用される方法をいくつかリストします。

  • 参加ベース
  • 揮発性に基づく
  • 同期に基づく
  • reentrantLockに基づく
  • countDownLatchに基づく

強く押しすぎないで、才能を発揮してください

まず始めましょう:参加する

joinはThreadクラスのメソッドであり、最下層はwait + notifyに基づいています。このメソッドはキューカットとして理解できます。呼び出しがキューをカットする人は誰でも、制限があります。

スレッド数が少ないシナリオに適しています。スレッド数が多すぎると、人形が無限に入れ子になり、少し面倒でエレガントではありません。

public class JoinTest {
    
    
    // 用来记录啊鸡学习时间
    static double year;

    public static void main(String[] args) {
    
    
        //线程A,练习唱跳rap
        Thread threadA = new Thread(() -> {
    
    
            for (year = 0.5; year <= 5; year += 0.5) {
    
    
                System.out.println("开始练习唱跳rap:已练习" + year + "年");
                try {
    
    
                    Thread.sleep(288);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                //众所周知,练习两年半即可出道
                if (year == 2.5) {
    
    
                    System.out.println("===========================>练习时长两年半,出道!!!");
                    //留意下这个break,想想如果不break会怎样
                    break;
                }
            }
        });
        //线程B,练习打篮球
        Thread threadB = new Thread(() -> {
    
    
            try {
    
    
            	// 让threadA线程插队,threadB执行到这儿时会被阻塞,直到threadA执行完
                threadA.join();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("开始练习打篮球");
        });
        // 启动线程
        threadA.start();
        threadB.start();
    }
}

ここに画像の説明を挿入

何度走っても結果は同じで、今日イエス様が来られても同じです、と私は言いました

中断しないと、当然、threadAがthreadBの実行を終了するのを待ってから、実行を開始します。
ここに画像の説明を挿入

揮発性を介して

この実装は比較的シンプルでわかりやすいですが、パフォーマンスが悪く、CPUリソースを大量に消費するため、不要な場合は使用しないでください。

public class VolatileTest {
    
    
    //定义一个共享变量用来线程间通信,注意用volatile修饰,保证它内存可见
    static volatile boolean flag = false;
    static double year;

    public static void main(String[] args) {
    
    
        //线程A,练习唱跳rap
        Thread threadA = new Thread(() -> {
    
    
            while (true) {
    
    
                if (!flag) {
    
    
                    for (year = 0.5; year <= 5; year += 0.5) {
    
    
                        System.out.println("开始练习唱跳rap:已练习" + year + "年");
                        try {
    
    
                            Thread.sleep(288);
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                        //众所周知,练习两年半即可出道
                        if (year == 2.5) {
    
    
                            System.out.println("===========================>练习时长两年半,出道!!!");
                            // 通知threadB你可以执行了
                            flag = true;
                            //同样留意这个break
                            break;
                        }
                    }
                    break;
                }
            }
        });
        //线程B,练习打篮球
        Thread threadB = new Thread(() -> {
    
    
            while (true) {
    
    
            	// 监听flag
                if (flag) {
    
    
                    System.out.println("开始练习打篮球");
                    break;
                }
            }
        });
        threadA.start();
        threadB.start();
    }
}

結果は上記の最初の結果と同じなので、表示しません。
ブレークについては、ここでは説明しません。最初に結果を推測してから、デモをコピーして実行します。実行する必要があります。

同期、wait()、notify()スリーピースセット

wait()とnotify()はどちらもObjectクラスの通信メソッドです。waitとnotifyは同期して使用する必要があることに注意してください。notifyはロックを解放しないことに注意してください。ロックが解放されない場所については、デモは以下に説明します。

public class SynchronizedTest {
    
    
    static double year;

    public static void main(String[] args) {
    
    
        SynchronizedTest sync= new SynchronizedTest();
        sync.execute();
    }

    public void execute() {
    
    
        //线程A,练习唱跳rap
        Thread threadA = new Thread(() -> {
    
    
            synchronized (this) {
    
    
                for (year = 0.5; year <= 5; year += 0.5) {
    
    
                    try {
    
    
                        System.out.println("开始练习唱跳rap:已练习" + year + "年");
                        Thread.sleep(288);
                        if (year == 2.5) {
    
    
                            System.out.println("===========================>练习时长两年半,出道!!!");
                            //唤醒等待中的threadB,但threadB不会立马执行,而是等待threadA执行完,因为notify不会释放锁
                            notify();
                            break;
                        }
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        });
        //线程B,练习打篮球
        Thread threadB = new Thread(() -> {
    
    
            synchronized (this) {
    
    
                try {
    
    
                    wait();
                    System.out.println("开始练习打篮球");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
        //注意,一定要先启动B,不然会导致B永远阻塞
        threadB.start();
        threadA.start();
    }
}

threadAの中断についてもっと考える必要があります。実行後、ロックを解放しないことの意味がわかります
。中断がない場合、threadAはthreadBをウェイクアップした後も独自のロジックを実行し続け、ロックを解放します。実行終了後threadBの実行開始時

ReentrantLockに基づく

ReentrantLockは、jucパッケージの同時実行ツールです。実装することもできますが、比較的複雑です。条件の待機とシグナルと組み合わせる必要があります。基本的な原則は、上記の待機と通知に少し似ています。

これが放課後の宿題です:なぜロックを解除する必要があるのか​​考えてみてください。

public class ReentrantLockTest {
    
    
    static double year;

    public static void main(String[] args) {
    
    
    	//实例化一个锁和Condition
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        //线程A,练习唱跳rap
        Thread threadA = new Thread(() -> {
    
    
            lock.lock();
            try {
    
    
                for (year = 0.5; year <= 5; year += 0.5) {
    
    
                    System.out.println("开始练习唱跳rap:已练习" + year + "年");
                    Thread.sleep(288);
                    //众所周知,练习两年半即可出道
                    if (year == 2.5) {
    
    
                        System.out.println("===========================>练习时长两年半,出道!!!");
                        //唤醒等待中的线程
                        condition.signal();
                        //这里的break也是个彩蛋,去掉它触发隐藏关卡
                        break;
                    }
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                //解锁
                lock.unlock();
            }
        });
        //线程B,练习打篮球
        Thread threadB = new Thread(() -> {
    
    
            lock.lock();
            try {
    
    
                //让当前线程等待
                condition.await();
                System.out.println("开始练习打篮球");
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                lock.unlock();
            }
        });
        //必须保证B先拿到锁,不然会导致A永远阻塞
        threadB.start();
        threadA.start();
    }
}

CountDownLatchに基づく

これもjucパッケージの同時実行ツールです。countDownと
awaitの2つの一般的な方法があります。countが1ずつ減少する前にすでに0の場合、何も起こりません。1ずつ減少した後に0になると、待機中のすべてのスレッドがウェイクアップされます。 up; awaitメソッドは、カウントが0になるまで現在のスレッドを待機させます

public class CountDownLatchTest {
    
    
    static double year;

    public static void main(String[] args) {
    
    
    	//实例化一个CountDownLatch,count设置为1,也就是说,只要调用一次countDown方法就会唤醒线程
        CountDownLatch latch = new CountDownLatch(1);
        //线程A,练习唱跳rap
        Thread threadA = new Thread(() -> {
    
    
            for (year = 0.5; year <= 5; year += 0.5) {
    
    
                System.out.println("开始练习唱跳rap:已练习" + year + "年");
                try {
    
    
                    Thread.sleep(288);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                //众所周知,练习两年半即可出道
                if (year == 2.5) {
    
    
                    System.out.println("===========================>练习时长两年半,出道!!!");
                    //计数器减一
                    latch.countDown();
                    //老规矩,去掉break触发隐藏关卡
                    break;
                }
            }
        });
        //线程B,练习打篮球
        Thread threadB = new Thread(() -> {
    
    
            try {
    
    
                //阻塞当前线程,计数器为0时被唤醒
                latch.await();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("开始练习打篮球");
        });
        threadA.start();
        threadB.start();
    }
}

仕上げ作業

マスター羅市庁舎
ここに画像の説明を挿入

上記の5つのデモを理解していれば、マルチスレッドに精通しています。おめでとうございます。

私が特別に設計したデモのthreadAに中断があります。中断の有無にかかわらず、実行結果を観察してください。多くのメリットが得られると思います。


了解しました

おすすめ

転載: blog.csdn.net/qq_33709582/article/details/121900989