3

私はJavaを独学で学んでおり、マルチスレッドについて理解したいと思っています。MyThread条件が真になるまでループを実行し、それに関する情報を出力するジョブを使用してクラスを作成しました。leftこれには、コンストラクターでインクリメントされ、ジョブが完了するとデクリメントされる静的フィールドが含まれています。クラスFlagは、いつ開始するかをスレッドに「伝える」ことです。残念ながら、すべてのスレッドが実行されると、leftゼロに等しくなりません。私はいくつかの方法を作りsynchronizedました、そしてそれは良くなりました、しかしそれはまだ完璧ではありません。私は何が間違っているのですか?

import java.util.Random;

class Flag
{
    private boolean ok = false;
    synchronized boolean ok()
    {
        return ok;
    }
    synchronized void setOk(boolean ok)
    {
        this.ok = ok;
    }
}

class MyThread extends Thread
{
    static int left = 0;

    synchronized void add()
    {
        left++;
    }

    synchronized int remove()
    {
        return --left;
    }

    String name;
    Flag flag;
    Random rnd = new Random();
    public MyThread(String name, Flag flag)
    {
        this.name = name;
        this.flag = flag;
        add();
    }

    public void run()
    {
        while(!flag.ok());

        double rnd;
        long count = 0;
        do{
            count++;
            rnd = Math.random();
        } while(rnd > 0.00001);
        print(count);
    }

    synchronized void print(long count)
    {
        System.out.printf("%s %10d left: %3d%n", name, count, remove());
    }
}

public class Test
{
    public static void main(String... args) throws Exception
    {
        Flag flag = new Flag();
        for(int i=0; i<2000; i++){
            new MyThread(String.format("%04d",i),flag).start();
        }

        flag.setOk(true);
    }
}
4

3 に答える 3

3

静的変数でのオブジェクトの同期が原因で予期しない結果が発生しました。

私はあなたのコードをほとんど変更せずにテストしました。Synchronized内の静的変数の同期が必要なため、MyThread.classでブロックを使用しましたMyThread

void add() {
    synchronized (MyThread.class) {
        left++;
    }
}

int remove() {
    synchronized (MyThread.class) {
        return --left;
    }
}

期待される結果を出力するたびに。AtomicInteger変数を使用することもできます。これは、アトミックにインクリメントされるカウンターなどのアプリケーションで使用されます。

于 2013-03-17T08:19:43.000 に答える
2

++left不可分操作ではあり--leftません。複数のスレッドが実行しようとしている場合、2つleftが同時にデクリメントしようとする場合があります。printこの動作は、コードがインスタンスレベル(インスタンスメソッド)で同期しているのに対しleft、はstatic(クラス)変数であるためです。

print()で出力される値は順序付けられていないことにも注意してください(print静的に同期されていないため、出力される「最後の」値は、呼び出された最後のスレッドの値ではない可能性がありますprint)。

最初の変更:leftすべてのスレッドが実行された後、それが実際にゼロであることを確認します。

public static void main(String... args) throws Exception
    {
        java.util.List<Thread> threads = new java.util.LinkedList<Thread>();
        Flag flag = new Flag();

        for(int i=0; i<20; i++){
            Thread thread=new MyThread(String.format("%04d",i),flag);
            threads.add(thread);
            thread.start();
        }

        flag.setOk(true);            
        for (Thread thread:threads) thread.join();
        System.out.println(MyThread.left);
    }

出力:

0003       9527 left:  19
0000      56748 left:  18
0006      11428 left:  17
0016     181845 left:   2
0010      95287 left:   3
0017     137911 left:   4
0018     432172 left:   5
0019     280280 left:   6
0013     421170 left:   7
0012     135830 left:   8
0015     104375 left:   9
0014     207409 left:  10
0001      16157 left:  11
0004     160136 left:  12
0008      31673 left:  13
0002      14589 left:  14
0005      23692 left:  15
0009      83419 left:  16
0011     231135 left:   0
0007     202603 left:   1
0

2番目の変更:クラスで同期します(addremoveおよびprintは静的メソッドに変換されます。静的メソッドからは表示されなくなったため、 printinへの呼び出しも置き換える必要があります)。runnameprint

  synchronized static void add()
    {
        left++;
    }

  synchronized static int remove()
    {
        return --left;
    }

  synchronized static void print(long count, String name)
    {
        System.out.printf("%s %10d left: %3d%n", name, count, remove());
    }

  public void run()
    {
        ...
        print(count,name);
    }  

出力:

0012      10207 left:  19
0006     121343 left:  18
0000      16236 left:  17
0008      81429 left:  16
0010      20250 left:  15
0002      14687 left:  14
0015      11051 left:  13
0017      23602 left:  12
0019      19651 left:  11
0005     180155 left:  10
0014     126578 left:   9
0003      41790 left:   8
0016      98362 left:   7
0001      96047 left:   6
0004     334071 left:   5
0009      46827 left:   4
0018     102826 left:   3
0013      71625 left:   2
0007     267208 left:   1
0011     188743 left:   0
0
于 2013-03-17T08:11:48.187 に答える
1

スレッド間で情報を交換するときは、プレーン変数を使用しないでください。スレッドを独自のメモリを持つプロセスとして扱います。情報の交換方法を決定するには、次の質問をします。

  1. BlockingQueueは私のニーズに合っていますか?BlockingQueueは、スレッド間で情報を交換するための最も一般的なメディアです。

  2. もしそうなら、私は本当にデータのチャンクを送信する必要がありますか、それとも信号の数を数えるだけで済みますか?共有カウンターには、セマフォ、原子番号、カウントダウンラッチなど、いくつかの種類があります。あなたの場合、AtomicIntegerで十分に見えます。

  3. そうでない場合、他の準備ができているタイプの同期コレクションは機能しますか?

  4. ニーズに合った同期データ交換クラスが用意されていない場合は、独自のクラスを作成してください。

于 2013-03-17T10:36:38.727 に答える