-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

按顺序打印数组的最简单方法是使用一个线程。您可以更改您使用的代码,run()而不是start() 或者您可以让一个线程进行打印,而其他线程什么也不做。

这是因为线程被设计为在它们具有可以同时执行的独立任务时工作得最好。当您有必须以特定顺序发生的任务时,请使用一个线程。

顺便说一句,您的同步没有做任何有用的事情,因为每个线程都在锁定不同的对象。您需要在每个线程中锁定同一个对象以共享资源(这不会解决您的问题,但它至少是线程安全的)显而易见的选择是在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();
    }
}

获取您的代码,您有 4 个不同Runnablesynchronized void run()方法实例。

这意味着每个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,它会抛出异常。这只能正常工作,因为代码确保在任何时候只有一个线程调用此函数。应该在此函数中为您的代码而不是我正在打印的内容打印数组值。

测量执行时间确认所有线程都花费了大约 60% 的时间等待,因此与几乎完全花费在String.format. 因此,对于更复杂的代码来说,这是一个非常糟糕的收获。

于 2013-11-10T23:14:01.333 回答