如何同步这些线程?

时间:2013-11-10 19:09:36

标签: java multithreading

在这个程序中,我创建了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();
         }   
    }   
}

3 个答案:

答案 0 :(得分:0)

按顺序打印数组的最简单方法是使用一个线程。您可以更改使用的代码run()而不是start()或者您可以让一个线程执行打印而其他线程不执行任何操作。

这是因为线程设计为具有可以同时执行的独立任务时效果最佳。当您具有必须按特定顺序发生的任务时,请使用一个线程。

顺便说一下你的同步没有做任何有用的事情,因为每个线程都锁定了一个不同的对象。您需要在每个线程中锁定相同的对象以共享资源(这不会解决您的问题,但它至少是线程安全的)显而易见的选择是在array对象上进行同步

答案 1 :(得分: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

要执行此操作,您只需向{em>同步添加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 方法可以看作是一种自身同步的方法。

这意味着

synchronized void method(){
    doSomething();
}

可以看作是

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

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

这意味着每个run将在不同的对象(Runnable实例)上进行同步,这就是为什么根本没有同步的原因。

要“一起工作”,线程必须在同一个对象上同步!

答案 2 :(得分:0)

首先:你以错误的方式使用线程。根据定义,Threads的执行顺序是 undefined ,因此它们在控制台上打印的顺序同样未定义。如果使用线程,则必须执行一个任务,其中执行顺序(读取结果)不重要(如不重要或稍后排序)或您不能使用线程。

如果你现在想要按照它们的顺序同步线程,这意味着如果你有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%的时间等待,因此与单线程执行相比,我们的速度提高了约20%,而单线程执行几乎完全花费在String.format上。所以这对于更复杂的代码来说是一个非常糟糕的收获。