2015-04-07 2 views
1

Я пытаюсь реализовать сортировку слияния, используя только синхронизацию wait/notify. Я знаю о более высокоуровневых конструкциях, таких как Fork/Join, Executors. и т. д. Но мне нужно использовать работу/уведомлять здесь. На основании этого https://courses.cs.washington.edu/courses/cse373/13wi/lectures/03-13/ я переработан метод parallelMergeSort() с синхронизированными блоками:алгоритм сортировки слияния Java с wait()/notify() synchronization

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 является входным массивом здесь.

В результате я вижу, что производительность сортировки снижается, и она медленнее, чем однопоточная сортировка. Я предполагаю, что узкое место находится в двух блоках синхронизации левой и правой частей массива. Кто-нибудь знает, как реорганизовать его, чтобы сделать его быстрее, чем однопоточная сортировка?

+0

Почему вы синхронизируете левый и правый массив? Идея состоит в том, что Threads каждый из них сортирует другую часть массива, поэтому нет необходимости в синхронизации. –

+0

@Fank, но мне нужно подождать эти потоки перед слиянием правой и левой частей, а wait() должен находиться в секции синхронизации. –

+0

@kolya Ну, я не думаю, что ждать() особенно полезно здесь. Wait используется, когда 1 поток хочет получить доступ к ресурсу, который может быть заблокирован другими параллельными алгоритмами потока, пытается избежать этого, работая на разных частях данных одновременно. Скорее всего, вы хотите присоединиться. Механизм Wait/notify полезен при необходимости использования Mutex или Semaphor, как для проблемы поставщика/потребителя. –

ответ

1

Проблема заключается в ваших вложенных 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 части может продолжаться, но инициирующая нить все еще держит right замок во время ожидания на left. После того, как суб-поток завершит обработку left, он назовет notify, а затем отпустит блокировку left, которая позволяет wait() переупаковывать ее и возвращать.

Затем инициирующее поток может вызвать right.wait(), который будет освободить right замок и позволяет другие подразделы нити, чтобы начать свою работу, следовательно, равный последовательную работу. Для каждого нереста подпотоков подчиненные потоки принудительно запускаются один за другим из-за блокировок, удерживаемых инициирующим потоком.

Один из способов исправить это будет первым запуском потоков и последующим приобретением замков, и только одна блокировка, которую вы собираетесь сделать до wait, а не гнездовые блоки .Это все еще зависит от неуточненного времени (теперь подпоток, возможно, завершил свою работу и вызвал notify еще до того, как вы введете блок synchronized(x) { x.wait(); }) и так называемые поддельные пробуждения. Проще говоря, вам нужно проверяемое условие, которое проверяется до и после вызова wait() как объяснено в documentation of wait():

Как и в одном аргументе версии, прерывание и ложные пробуждения возможны, и этот метод всегда должен быть использован в петля:

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

условие может быть boolean флаг установлен в true по суб-нить прямо перед вызовом notify() сигнализировать о том, что работа выполнена.

Обратите внимание, что это все, что вы получаете бесплатно при использовании Thread.join(). Синхронизация происходит в методе join(), и эти два вызова не могут пересекаться. Кроме того, реализация использует проверяемое условие (живое состояние потока), чтобы обеспечить вызов wait() только при необходимости и защитить себя от «побочных пробуждений».

+0

Я изменил синхронизации блоков следующим образом: 'синхронизированного (lThread) { \t \t \t \t \t в то время (правда) { \t \t \t \t \t \t если \t ((lThread.isAlive (!))) \t \t \t \t \t \t break; \t \t \t \t \t \t lThread.wait (0L); \t \t \t \t \t} \t \t \t \t} \t \t \t \t синхронизирована (RThread) { \t \t \t \t \t в то время (правда) { \t \t \t \t \t \t если (! (RThread.isAlive())) \t \t \t \t \t \t \t break; \t \t \t \t \t \t rThread.wait (0L); \t \t \t \t \t} \t \t \t \t} '' Добавлена ​​synchronized' на весь метод и изменил уведомление 'this.notify()' ' –

+0

ли this' в' Thread' Например, вы ждете от? В вашем вопросе вы используете делегацию. Кстати, 'while (true) {if (! X) break; ...} 'всегда можно заменить на' while (x) {...} ' – Holger

+0

yes, thread. Я просто посмотрел на исходный код 'join()' и повторно используемую логику синхронизации JDK. –

0

Вам нужно будет отсортировать миллионы значений, чтобы увидеть эффект параллелизма, если вы когда-либо делаете это, потому что вы копируете массивы повсюду и накладываете большую нагрузку на систему на доступ к памяти и сборку мусора , а не сортировки.

Для правильной параллелизации сортировки вам нужно будет сделать это на месте - что делает слияние очень маловероятным, чтобы быть хорошим кандидатом, потому что он должен создать новый массив для цели.

Если все, что вы делаете, это эксперимент, используйте алгоритм сравнения/вычисления с интенсивным вычислением, подобный сортировке пузырьков.

Обратите внимание, что если это задано как задание, то ваш лектор, вероятно, ожидает, что вы ответите , вы не можете, потому что merge-sort является плохим кандидатом на параллелизм.

+0

Я пробовал до 32 м значений, как они там, https://courses.cs.washington.edu/courses/cse373/13wi/lectures/03-13/MergeSort.java И он работал хорошо, как ожидалось, до моих изменений с ожиданием/уведомлением. –

+0

@kolya_metallist - также помните, чтобы ограничить количество потоков чуть ниже числа ядер, на которых вы работаете. – OldCurmudgeon

+0

Я использую 'Runtime.getRuntime(). AvailableProcessors();' Он возвращает 4, я также использовал 8, он показал тот же результат. –