クリックして公式アカウントをフォローすると、Javaの乾物が間に合うように配達されます
質問1なぜではなく、
ほとんどの人は、同期されたコードの一般的な使用法を知っています。
synchronized (obj) {
while (check pass) {
wait();
}
// do your business
}
だから問題は、なぜこれがifではなくwhileなのかということです。
制限付きキューを実装する
制限付きキューを想像したいとします。その場合、一般的なコードは次のようになります。
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つあると仮定すると、次の手順が実行されると想定します。
Aはロックライン0を取得しました
サイズ==0を検出し(1行目)、待機状態に入り、ロックを解除します(2行目)。
この時点で、Bはロックline0を取得し、size == 0を検出し(1行目)、待機状態に入り、ロックを解放しました(2行目)。
このとき、スレッドCがデータ1を追加すると、notifyAllの待機中のスレッドがすべて起動されます。
ABは、Aが再びロックを取得したと想定して、ロックを再取得します。次に、3行目に移動し、データを削除します(4行目)。
データを削除した後、他の人に通知したいのですが、このときリストのサイズが変わったので、notifyAll(5行目)を呼び出します。このとき、Bが起きてからBが下がり続けます。
このとき、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の乾物が間に合うように配達されます
質問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が教えてくれることです。アーキテクトになりたい場合は、このアーキテクトマップを見て、迂回を避けることをお勧めします。
C1は来てそれを手に入れたい、C2、C3は手に入れるのを待っている
C1は実行を開始し、1を取得してから、notifyを呼び出して終了します
C1がC2をウェイクアップする場合、P2(他のすべては待機する必要があります)はputメソッドでのみ待機できます(同期化された(この)モニターの取得を待機しています)
C2はwhileループをチェックし、この時点でキューが空であることを検出したため、待機します
C3もP2の前に実行され、それも空であり、待機することしかできないことがわかります。
このとき、P2、C2、C3がすべてロックを待っていることがわかり、最後にP2がロックを取得し、1を入れて通知し、終了しました。
参照:
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
Spring Cloudはリスクの高い脆弱性を爆発させ、それらを迅速に修正します!
Spring Bootが機密性の高い構成を保護する4つの方法!
さようならシングル!Javaでオブジェクトを作成する6つの方法
AnotherRedisDesktopManagerが充電を開始しましたか?
bang-bangクラスを書くのをやめて、デコレータパターンを試してください!
プログラマーはさまざまな技術システムに精通しており、45歳で仕事を見つけるのは難しいです!
Spring Boot 3.0 M1がリリースされ、Java8が正式に非推奨になりました
より多くのドライグッズを見るには、Javaテクノロジースタックに注意してください
スプリングブートコンバットノートを入手!