Java 合并排序算法与 wait()/notify() 同步

Java merge sort algorithm with wait()/notify() synchronization

我正在尝试仅使用 wait/notify 同步来实现合并排序。我知道更多高级结构,例如 Fork/Join,Executors。等等,但我需要在这里使用 work/notify 。基于此https://courses.cs.washington.edu/courses/cse373/13wi/lectures/03-13/我用同步块重构了方法parallelMergeSort()

public void parallelMergeSort() {
  synchronized (values) {
     if (threadCount <= 1) {
        mergeSort(values);
        values.notify();
     } else if (values.length >= 2) {
        // split array in half
       int[] left = Arrays.copyOfRange(values, 0, values.length / 2);
       int[] right = Arrays.copyOfRange(values, values.length / 2, values.length);
       synchronized(left) {
         synchronized (right) {
            // sort the halves
            // mergeSort(left);
            // mergeSort(right);
           Thread lThread = new Thread(new ParallelMergeSort(left, threadCount / 2));
           Thread rThread = new Thread(new ParallelMergeSort(right, threadCount / 2));
           lThread.start();
           rThread.start();
           /*try {
             lThread.join();
             rThread.join();
           } catch (InterruptedException ie) {}*/
           try {
             left.wait();
             right.wait();
           } catch (InterruptedException e) {
             e.printStackTrace();
           }
           // merge them back together
           merge(left, right, values);
        }
      }
      values.notify();
    }
  }
}

values这里是一个输入数组。

结果我发现排序的性能下降了,甚至比单线程排序还慢。我猜瓶颈在数组左右部分的两个同步块内。有人知道如何重构它以使其比单线程排序更快吗?

您将需要对数百万个值进行排序以查看并行性的效果,如果您这样做的话,因为您正在到处复制数组,这给系统带来了内存访问和垃圾方面的大部分压力collection,排序不上

要正确地并行排序,您需要这样做 in-place - 这使得合并排序不太可能成为一个好的候选者,因为它必须为目标创建一个新数组。

如果您所做的只是试验,那么请使用 compare/compute 密集型算法,例如 bubble-sort。

请注意,如果这已被设置为作业,那么您的讲师可能希望您回答 您不能,因为 merge-sort 不适合并行处理.

问题出在嵌套的 synchronized 块中:

synchronized(left) {
   synchronized (right) {
       Thread lThread = new Thread(…);
       Thread rThread = new Thread(…);
       lThread.start();
       rThread.start();
       try {
         left.wait();
         right.wait();
       }
       …

当您启动新线程时,您同时持有这两个锁,而新线程又试图获取这些锁。因此,在启动线程释放这些锁之前,您的新线程将被阻塞。当线程调用 wait() 但是 时会隐式发生这种情况……您一次只能等待一个条件!

因此,当发起线程调用 left.wait() 时,它会释放 left 实例的锁,并且为处理 left 部分而产生的子线程可以继续,但发起线程在等待 left 时仍然持有 right 锁。一旦子线程完成处理 left 它将调用 notify,然后释放 left 的锁,允许 wait() 重新获取它和 return.

然后 启动线程可以调用 right.wait() 这将释放 right 锁并允许其他子线程开始工作,因此等于顺序性能。对于每个子线程的生成,由于启动线程持有锁,子线程一个接一个地被强制执行 运行。

解决此问题的一种方法是先启动线程,然后再获取锁,并且只获取您即将 wait 的一个锁,而不是嵌套 synchronized 块。这仍然受制于未指定的时间(现在,子线程可能已经完成其工作并甚至在您进入 synchronized(x) { x.wait(); } 块之前调用 notify)和所谓的 spurious唤醒。简单地说,您需要一个可验证的条件,该条件在调用 wait() 之前和之后进行检查,如 documentation of wait():

中所述

As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:

synchronized (obj) {
    while (<condition does not hold>)
        obj.wait();
    ... // Perform action appropriate to condition
}

条件可能是子线程在调用 notify() 之前将 boolean 标志设置为 true 以表明工作已完成。

请注意,这一切都是您在使用 Thread.join() 时免费获得的。同步发生在 join() 方法中,这两个调用不能重叠。此外,该实现使用可验证条件(线程的活动状态)来确保仅在必要时调用 wait() 并保护自己免受“虚假唤醒”。