3

私はいくつかのJava並行性の概念と使用されているものを学び始めます。しかし、このコードの1つは私の理解を超えています。

public class Count { 
    private int count = 0; 
    public synchronized void setCount(int count) { 
        this.count = count; 
    } 
    public synchronized int getCount() { 
        return count; 
    } 
} 
class CountRunner extends Thread { 
    Count count; 
    public CountRunner(Count count) { 
        this.count = count; 
    } 
    public void run() { 
        for (int i = 1; i <= 1000; i++) { 
            count.setCount(count.getCount() + 1); 
        } 
    } 
} 
class TestCount { 
    public static void main(String[] args) throws Exception { 
        Count count = new Count(); 
        CountRunner runnerA = new CountRunner(count); 
        CountRunner runnerB = new CountRunner(count); 
        runnerA.start(); 
        runnerB.start();         
        runnerA.join(); //join statement here 
        runnerB.join(); 
        System.out.println("count.getCount = " + count.getCount()); 
    } 
} 
質問:
1.結果は、何度も2000を少し下回るのですが、なぜですか?
2. 2つのjoin()ステートメントを削除する場合、なぜcount.getCount = 451、さらに少ないのですか?
3. join()ステートメントを削除しても効果はないと思います。
毎回1つのオブジェクトを1つのスレッドにロックするSynchronizedメソッドをすでに持っているので?
では、Synchronizedとjoin()を使用する意味は何ですか?
4

5 に答える 5

4

とてもシンプルです。getCount + 1 を呼び出して setCount メソッドを呼び出します。メソッドに入る前に、ランタイムは getCount (同期) を評価しますが、getCount を出て setCount に入るときにロックを保持せず、他のスレッドが呼び出し getCount に入ることができます。そのため、時々 2 つ (作成するスレッドの数によってはそれ以上) のスレッドが getCount で同じ値を持つことがあります。スレッド A が getCount に値 1 を入力して受け取るとします。ランタイムは、getCount を呼び出して同じ値 1 を受け取るトレッド B に実行をもたらします。スレッド B は値を 1 に設定し、さらに 50 回実行するため、その段階でのカウントは 50 になります。ランタイムは、setCount を 1 で呼び出すスレッド A に実行を譲ります (setCount を呼び出すことができず、exec を譲ったことを思い出してください)。ここで、A は値を 1 に設定します (これは間違っています)。

次のように実装を変更します。

public void run() { 
    for (int i = 1; i <= 1000; i++) {
      synchronized(count){ 
        count.setCount(count.getCount() + 1); 
      }
    } 
} 
于 2012-02-27T05:51:24.027 に答える
4
  1. 一線を越えたら

    count.setCount(count.getCount() + 1);

3 つの行に分けると、より明確になります。

final int oldCount = count.getCount(); // a
final int newCount = oldCount + 1; // b
count.setCount(newCount); // c

ステートメント (a) と (c) はそれぞれ同期されていますが、ブロック全体は同期されていないことに注意してください。したがって、それらは引き続きインターリーブできます。つまり、スレッド B がスレッド (a) を実行した後、スレッド A はステートメント (a) に入る/実行できますが、ステートメント (c) を終了/入力する前に実行できます。これが発生すると、スレッド (a) と (b) のoldCountが同じになり、結果としてインクリメントが 1 つ失われます。

2.

join() は、印刷する前にスレッド A とスレッド B の両方が終了していることを確認するためのものです。結果を出力するときに、スレッド A と B がまだ実行を終了していない可能性があるため、カウントが小さい理由です。つまり、join() を使用せずに完全に同期したとしても、まだ 2000 よりもはるかに小さい数値になります。

3. 2 の回答を参照してください。

于 2012-02-27T06:07:09.293 に答える
3

1) 正しくロックしていないため。getCount()ロックし、値を取得してロックを解除し、どのロックをインクリメントして呼び出しsetCount()、値を保存してロックを解除します。場合によっては、両方のスレッドが を呼び出しgetCount()、最初のスレッドがロックし、値を取得してxロックを解除することがあります。次に、2 番目のスレッドがロックを取得し、同じ値を取得してロックxを解除します。両方のスレッドがインクリメントし、後で同じ値を保存するため、予想よりも少ないカウントが得られます。

2)join()スレッドが終了するのを待たずにgetCount()、スレッドがまだ実行されている間にメインスレッドが呼び出してランダムな値を取得するだけです。

3) 他のスレッドは常にロックを保持しているわけではないため(両方を実行したい場合は、結局のところ、お互いにロックを解除する時間を与える必要があります)、join()静止画が必要になります。

于 2012-02-27T05:52:00.907 に答える
1

someThread.join()が完了するまで呼び出しThreadを待機させます。someThread

呼び出しを削除すると、カウントがまだ実行されている可能性があるため、カウントが確定する前にjoin()メインThreadが呼び出される可能性があります。getCount()someThread

同期されたメソッドとは、 への複数の同期された呼び出しを同時にObject行うことができないことを意味します。

于 2012-02-27T05:47:34.990 に答える
1

1行の答えは、それcount.setCount(count.getCount() + 1);はアトミック操作ではないということです。

または、少し控えめに言うと、setCountgetCountは適切にスレッドセーフでアトミックですが、このスレッドの と の呼び出しの間にsetCount、別のスレッドがこれらのメソッドのいずれかを呼び出すのを止めるものは何もありませんgetCount。その結果、カウントが失われます。

カウントが失われないようにする 1 つの方法は、アトミックincrement()操作を作成することです。

于 2012-02-27T06:11:14.093 に答える