インタビュアー:wait()メソッドをどのように呼び出しましたか?ifまたはwhileを使用しますか?誤解しないでください!

クリックして公式アカウントをフォローすると、Javaの乾物が間に合うように配達されますb12aa1e5c723a29d9168c776bc78507f.png

質問1なぜではなく、

ほとんどの人は、同期されたコードの一般的な使用法を知っています。

synchronized (obj) {
     while (check pass) {
        wait();
    }
    // do your business
}

だから問題は、なぜこれがifではなくwhileなのかということです。

この質問は最初は長い間考えていましたが、すでに同期ブロックにあるので必要ありません。最近Stackoverflowの質問を読むまでは、これはいつも考えていたものです。そして私はそれについて何も知りませんでした。より深い理解。

制限付きキューを実装する

制限付きキューを想像したいとします。その場合、一般的なコードは次のようになります。

static class Buf {
    private final int MAX = 5;
    private final ArrayList<Integer> list = new ArrayList<>();
    synchronized void put(int v) throws InterruptedException {
        if (list.size() == MAX) {
            wait();
        }
        list.add(v);
        notifyAll();
    }

    synchronized int get() throws InterruptedException {
        // line 0 
        if (list.size() == 0) {  // line 1
            wait();  // line2
            // line 3
        }
        int v = list.remove(0);  // line 4
        notifyAll(); // line 5
        return v;
    }

    synchronized int size() {
        return list.size();
    }
}

ここで使用されている場合に注意してください。それでは、どのようなエラーが報告されるかを見てみましょう。

次のコードは、1つのスレッドを使用して配置し、10のスレッドを使用して取得します。

final Buf buf = new Buf();
ExecutorService es = Executors.newFixedThreadPool(11);
for (int i = 0; i < 1; i++)
es.execute(new Runnable() {

    @Override
    public void run() {
        while (true ) {
            try {
                buf.put(1);
                Thread.sleep(20);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
    }
});
for (int i = 0; i < 10; i++) {
    es.execute(new Runnable() {

        @Override
        public void run() {
            while (true ) {
                try {
                    buf.get();
                    Thread.sleep(10);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    });
}

es.shutdown();
es.awaitTermination(1, TimeUnit.DAYS);

このコードはすぐにまたは最初にエラーを報告します

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.remove(ArrayList.java:492)
at TestWhileWaitBuf.get(TestWhileWait.java:80)atTestWhileWaitBuf.get(TestWhileWait.java:80)atTestWhileWait2.run(TestWhileWait.java:47)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

明らかに、削除時にエラーが報告されました。最新のインタビューの質問は整理されており、Javaインタビューライブラリアプレットでオンラインで質問をブラッシングできます。

では、分析してみましょう。

get操作を実行するスレッドAとBが2つあると仮定すると、次の手順が実行されると想定します。

  1. Aはロックライン0を取得しました

  2. サイズ==0を検出し(1行目)、待機状態に入り、ロックを解除します(2行目)。

  3. この時点で、Bはロックline0を取得し、size == 0を検出し(1行目)、待機状態に入り、ロックを解放しました(2行目)。

  4. このとき、スレッドCがデータ1を追加すると、notifyAllの待機中のスレッドがすべて起動されます。

  5. ABは、Aが再びロックを取得したと想定して、ロックを再取得します。次に、3行目に移動し、データを削除します(4行目)。

  6. データを削除した後、他の人に通知したいのですが、このときリストのサイズが変わったので、notifyAll(5行目)を呼び出します。このとき、Bが起きてからBが下がり続けます。

  7. このとき、Bさんは、実はこの時の競合状態を満たしていない(size == 0)ので問題があります。Bさんは削除できると思って削除しようとしましたが、例外がなくなりました。

次に、修正は非常に簡単です。取得するためにしばらく追加するだけです:

synchronized int get() throws InterruptedException {
    while (list.size() == 0) {
        wait();
    }
    int v = list.remove(0);
    notifyAll();
    return v;
}

同様に、putのスレッド数とgetスレッドの数を変更して、put中にない場合は機能しないことを確認できます。

外部の定期的なタスクを使用して、現在のリストのサイズを印刷できます。サイズが最大5に固定されていないことがわかります。

final Buf buf = new Buf();
ExecutorService es = Executors.newFixedThreadPool(11);
ScheduledExecutorService printer = Executors.newScheduledThreadPool(1);
printer.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        System.out.println(buf.size());
    }
}, 0, 1, TimeUnit.SECONDS);
for (int i = 0; i < 10; i++)
es.execute(new Runnable() {

    @Override
    public void run() {
        while (true ) {
            try {
                buf.put(1);
                Thread.sleep(200);
            }
            catch (InterruptedException e) {
                 e.printStackTrace();
                break;
            }
        }
    }
});
for (int i = 0; i < 1; i++) {
    es.execute(new Runnable() {

        @Override
        public void run() {
            while (true ) {
                try {
                    buf.get();
                    Thread.sleep(100);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    });
}

es.shutdown();
es.awaitTermination(1, TimeUnit.DAYS);

ここで、なぜそれが間である必要があるのか​​、またはそうである必要があるのか​​を明確にする必要があると思います。クリックして公式アカウントをフォローすると、Javaの乾物が間に合うように配達されます000ff29510d2fe746f6c819f02920d12.png

質問2:notifyAllまたはnotifyをいつ使用するか

ほとんどの人はあなたにこれを言うでしょう:

全員に通知する場合はnotifyAllを使用し、1人だけに通知する場合はnotifyAllを使用します。

しかし、実際に通知するのは誰に通知するかを決めることはできないことは誰もが知っています(すべて待機セットから1つを選択します)。では、これのポイントは何ですか?

上記の例ではnotifyAllを使用したので、notifyが機能するかどうかを見てみましょう。さらに、マルチスレッドの一連のインタビューの質問と回答がすべて整理されています。WechatはJavaテクノロジースタックを検索し、バックグラウンドで送信します。インタビューはオンラインで読むことができます。

次に、コードは次のようになります。

synchronized void put(int v) throws InterruptedException {
    if (list.size() == MAX) {
        wait();
    }
    list.add(v);
    notify();
}

synchronized int get() throws InterruptedException {
    while (list.size() == 0) {
        wait();
    }
    int v = list.remove(0);
    notify();
    return v;
}

次の点は、JVMが教えてくれることです。アーキテクトになりたい場合は、このアーキテクトマップを見て、迂回を避けることをお勧めします。

いつでも、ウェイクアップされて実行されるスレッドは予測できません。たとえば、同じオブジェクトに5つのスレッドがある場合、実際には次にどのスレッドが実行されるかわかりません。同期されたセマンティクスにより、1つだけが有効になります。同期を実行するスレッド。ブロック内のコード。

次に、次のシナリオがデッドロックにつながると想定します。

P-プロデューサーはputを呼び出します

C-消費者の電話は

P1は1を置きます

P2は来て入れたかったので、いっぱいになって待っていました

P3は来て入れたかったので、いっぱいになって待っていました

C1は来てそれを手に入れたい、C2、C3は手に入れるのを待っている

C1は実行を開始し、1を取得してから、notifyを呼び出して終了します

C1がC2をウェイクアップする場合、P2(他のすべては待機する必要があります)はputメソッドでのみ待機できます(同期化された(この)モニターの取得を待機しています)

C2はwhileループをチェックし、この時点でキューが空であることを検出したため、待機します

C3もP2の前に実行され、それも空であり、待機することしかできないことがわかります。

このとき、P2、C2、C3がすべてロックを待っていることがわかり、最後にP2がロックを取得し、1を入れて通知し、終了しました。

この時点でP2はP3をウェイクアップし、P3はキューがいっぱいであることを検出します。キューが空になるのを待つ以外に方法はありません。この時点では他の呼び出しはないため、これら3つのスレッド(P3、C2 、C3)すべての変更が一時停止になっています。つまり、デッドロックされています。

参照:
http ://stackoverflow.com/questions/37026/java-notify-vs-notifyall-all-over-again

著作権表示:この記事は、CSDNブロガー「scugxl」によるオリジナルの記事であり、CC 4.0 BY-SAの著作権表示に従います。転載するには、元のソースリンクとこのステートメントを添付してください。元のリンク:https://blog.csdn.net/scugxl/article/details/71434083

842454c3cfb2d8a4c06da9f6c0696f0d.gif

7d9efdac5935704c81d90cb604e23fdc.png

Spring Cloudはリスクの高い脆弱性を爆発させ、それらを迅速に修正します!

2021年に起こっている10のビッグテックの事柄!

実際の23のデザインパターン(非常に完全)

Spring Bootが機密性の高い構成を保護する4つの方法!

さようならシングル!Javaでオブジェクトを作成する6つの方法

AliがLongAdderを推奨するのはなぜですか?

AnotherRedisDesktopManagerが充電を開始しましたか?

bang-bangクラスを書くのをやめて、デコレータパターンを試してください!

プログラマーはさまざまな技術システムに精通しており、45歳で仕事を見つけるのは難しいです!

Spring Boot 3.0 M1がリリースされ、Java8が正式に非推奨になりました

Spring Bootの調査ノート、これは完全すぎます!

より多くのドライグッズを見るには、Javaテクノロジースタックに注意してください

6974047e2a24ac6f0e2c41dea38922db.png

075ee7f6a9d9e8809b9ef89c8bd9bda9.gif

スプリングブートコンバットノートを入手!

おすすめ

転載: blog.csdn.net/youanyyou/article/details/123700220