-2

このプログラムでは、4 つのスレッドを作成し、それぞれが配列の 1/4 を出力しています。しかし、私はそれらを同期させたいと思っています。メソッド run() を作成するsynchronizedと、スレッドはまだ配列の一部をランダムに出力しています。

Thread 1:0
Thread 3:50
Thread 3:51
Thread 3:52
Thread 3:53
Thread 3:54
Thread 2:25
Thread 3:55
Thread 3:56
Thread 3:57
Thread 1:1
Thread 3:58
Thread 4:75
Thread 4:76
Thread 4:77
Thread 4:78
Thread 4:79
Thread 4:80
Thread 2:26

public class Main {

    public static void main(String[] args) {

         final int M = 100;
         final int N = 4;
         final int[] array = new int[M];

         for(int b = 0; b < M; b++) array[b] = b;

         for( int p = 0; p < N; p++) {
             final int i = p;
             new Thread(new Runnable() {
                 public synchronized void run() {
                         for(int a = i*(M/N);a < (i+1)*(M/N); a++)
                             System.out.println("Thread "+(i+1)+":"+array[a]); 
                     }
             }).start();
         }   
    }   
}
4

3 に答える 3

0

配列を順番に出力する最も簡単な方法は、1 つのスレッドを使用することです。run()の代わりに使用するコードを変更できます。start() または、1 つのスレッドで印刷を行い、他のスレッドは何もしないようにすることもできます。

これは、スレッドが同時に実行できる独立したタスクを持っている場合に最適に機能するように設計されているためです。特定の順序で発生する必要があるタスクがある場合は、1 つのスレッドを使用します。

ところで、各スレッドが異なるオブジェクトをロックしているため、同期は何も役に立ちません。リソースを共有するには、各スレッドで同じオブジェクトをロックする必要があります (これで問題は解決しませんが、少なくともスレッド セーフになります) array

于 2013-11-10T19:14:17.337 に答える
0

質問はまったく明確ではありませんが、各サブアレイを正しい順序で印刷したいと思うかもしれません。

これは、次のような出力を意味します。

Thread 1:0
Thread 1:1
Thread 1:2
Thread 1:3
Thread 1:4
Thread 1:5
Thread 1:6
Thread 1:7
Thread 1:8
Thread 1:9
Thread 1:10
Thread 1:11
Thread 1:12
Thread 1:13
Thread 1:14
Thread 1:15
Thread 1:16
Thread 1:17
Thread 1:18
Thread 1:19
Thread 1:20
Thread 1:21
Thread 1:22
Thread 1:23
Thread 1:24
Thread 4:75
Thread 4:76
Thread 4:77
Thread 4:78
Thread 4:79
Thread 4:80
Thread 4:81
Thread 4:82
Thread 4:83
Thread 4:84
Thread 4:85
Thread 4:86
Thread 4:87
Thread 4:88
Thread 4:89
Thread 4:90
Thread 4:91
Thread 4:92
Thread 4:93
Thread 4:94
Thread 4:95
Thread 4:96
Thread 4:97
Thread 4:98
Thread 4:99
Thread 3:50
Thread 3:51
Thread 3:52
Thread 3:53
Thread 3:54
Thread 3:55
Thread 3:56
Thread 3:57
Thread 3:58
Thread 3:59
Thread 3:60
Thread 3:61
Thread 3:62
Thread 3:63
Thread 3:64
Thread 3:65
Thread 3:66
Thread 3:67
Thread 3:68
Thread 3:69
Thread 3:70
Thread 3:71
Thread 3:72
Thread 3:73
Thread 3:74
Thread 2:25
Thread 2:26
Thread 2:27
Thread 2:28
Thread 2:29
Thread 2:30
Thread 2:31
Thread 2:32
Thread 2:33
Thread 2:34
Thread 2:35
Thread 2:36
Thread 2:37
Thread 2:38
Thread 2:39
Thread 2:40
Thread 2:41
Thread 2:42
Thread 2:43
Thread 2:44
Thread 2:45
Thread 2:46
Thread 2:47
Thread 2:48
Thread 2:49

これを行うには、同期先を追加するだけでよく、Objectようなものを使用できます。

final Object lock = new Object();

run次に、同期する方法を変更する必要がありますlock

public void run() {
    synchronized (lock) {
        for (int a = i * (M / N); a < (i + 1) * (M / N); a++) {
            System.out.println("Thread " + (i + 1) + ":" + array[a]);
        }
    }
}

完全に編集されたメインは次のとおりです。

public static void main(String[] args) {

    final int M = 100;
    final int N = 4;
    final int[] array = new int[M];
    final Object lock = new Object(); // LOCK!!

    for (int b = 0; b < M; b++) {
        array[b] = b;
    }

    for (int p = 0; p < N; p++) {
        final int i = p;
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) { // SYNCHRONIZED BLOCK INSTEAD OF SYNCHRONIZED METHOD
                    for (int a = i * (M / N); a < (i + 1) * (M / N); a++) {
                        System.out.println("Thread " + (i + 1) + ":" + array[a]);
                    }
                }
            }
        }).start();
    }
}

もちろん、内部の要素と同じ順序で配列を出力したいだけであれば、スレッドを扱う必要はありません。

編集:

コードが期待どおりに機能しない理由の簡単な説明:

同期されたメソッドは、それ自体で同期されたメソッドと見なすことができます。

この意味は

synchronized void method(){
    doSomething();
}

として見ることができます

void method(){
    synchronized(this){
        doSomething();
    }
}

コードにアクセスすると、Runnablewithsynchronized void run()メソッドの 4 つの異なるインスタンスがあります。

これは、それぞれrunが異なるオブジェクト (Runnableインスタンス) で同期することを意味し、それが同期がまったくない理由です。

「連携」するには、スレッドが同じオブジェクトで同期する必要があります。

于 2013-11-10T19:19:32.467 に答える
0

まず第一に、スレッドを間違った方法で使用しています。定義上、スレッドの実行順序は未定義であるため、コンソールに表示される順序も同様に未定義です。スレッドを使用する場合は、実行 (結果の読み取り) の順序が重要でない (重要ではない、または後でソートされるなど) タスクが必要であるか、スレッドを使用できません。

スレッドを順番どおりに同期する場合、4 つのスレッドがある場合、すべてのスレッドの 3/4 が常にブロックされていることを意味します。つまり、スレッドの時間の 3/4 が費やされます。待機中、実際に作業中の 1/4 のみ。1/4 * 4 = 1 であるため、事実上、シングルスレッド アプリケーションの実行速度からスレッド同期のオーバーヘッドを差し引いたものになります。

さらに、スレッドの run メソッドを同期することは、スレッド化を効果的に無効にすることを意味します。

それにもかかわらず、私はその質問が興味深いものであることに気づき、同期にPhaserを使用して要件を満たす次の (汚い) コードを考え出しました。最終的なコードは次のとおりです。

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Test {

  private static volatile int lastNumber = -1;

  private static void printNumber(int number) {
    if (number != lastNumber + 1) {
      throw new IllegalStateException("Tried to print numbers out of order!");
    } else {
      if (number % 20 == 19) {
        System.out.println(String.format("%3d, ", number));
      } else {
        System.out.print(String.format("%3d, ", number));
      }
      lastNumber = number;
    }
  }

  public static void main(String[] args) throws InterruptedException {
    final int MAX_NUMBER = 1000;
    final int NUM_THREADS = 4;

    final AtomicInteger number = new AtomicInteger(0);
    final Phaser phaser = new Phaser(NUM_THREADS);

    final ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
    for (int i = 0; i < NUM_THREADS; ++i) {
      executor.execute(new Runnable() {
        @Override
        public void run() {
          try {
            int numberPicked;
            while ((numberPicked = number.getAndIncrement()) < MAX_NUMBER) {
              while (phaser.getPhase() != numberPicked) {
                phaser.arriveAndAwaitAdvance();
              }
              printNumber(numberPicked);
              phaser.arriveAndAwaitAdvance();
            }
          } catch (IllegalStateException e) {
            System.err.println(e);
          } finally {
            phaser.arriveAndDeregister();
          }
        }
      });
    }

    executor.shutdown();
    executor.awaitTermination(3, TimeUnit.SECONDS);
  }
}

すべてのスレッドがフェーザーを使用して常に同期し、自分の番になった場合にのみ印刷するため、これは機能します。彼らは文字通り番号を選び、座って自分の番号が呼ばれるまで待ちます。これにより、スレッドは要求どおりに順番に印刷されます。

printNumberこの機能は便利なだけでなく、注文が正しいことを確認するためのものでもあることに注意してください。数値を順序付けするわけではありませんが、出力する数値が lastNumber + 1 でない場合は例外がスローされます。これは正常に機能するだけです。これは、コードが常に 1 つのスレッドだけがこの関数を呼び出すことを保証するためです。配列値の印刷は、私が印刷しているものではなく、コードのこの関数で行う必要があります。

実行時間を測定すると、すべてのスレッドが待機時間の約 60% を費やしていることが確認されるため、ほぼ完全にString.format. したがって、これははるかに複雑なコードでは非常に悪いゲインです。

于 2013-11-10T23:14:01.333 に答える