2010-05-01 3 views
-1

Я создаю графический калькулятор.Исключительное состояние гонки в Java

В попытке выжать из него еще большую производительность, я добавил многострочный калькулятор линии. По сути, моя текущая реализация состоит в построении потокобезопасных значений Queue значений X, а затем запускается, хотя и для многих потоков, которые ему нужны, каждый из которых вычисляет точку в строке, используя очередь для получения ее значений, а затем упорядочивает точки с помощью HashMap, когда расчеты выполнены. Эта реализация отлично работает, и это не то место, где мое состояние гонки (просто какая-то справочная информация).

При изучении результатов работы я обнаружил, что HashMap является узким местом производительности, поскольку я делаю это синхронно на одном потоке. Поэтому я решил, что упорядочение каждой точки, поскольку ее расчет будет работать лучше всего. Я пробовал PriorityQueue, но это было медленнее, чем HashMap.

Я в конечном итоге создать алгоритм, который по существу работает как это:

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

Затем я копирую этот список значений в другой класс, невообразимо и временно названный BlockingList, который отвечает за заказ очков по мере их расчета.

BlockingList содержит метод put(), который принимает в качестве параметров два значения BigDecimal s, а первое - значение X, второе - расчетное значение Y. put() принимает значение, только если значение X является следующим в списке, которое должно быть принято в списке значений X, и будет блокироваться до тех пор, пока другой поток не даст ему следующее исключенное значение.

Например, поскольку это может ввести в заблуждение, скажем, у меня есть две нити, Thread-1 и Thread-2. Thread-2 получает значение X 10.0 из очереди значений, а Thread-1 получает 9.0. Тем не менее, Thread-1 сначала выполняет вычисления, и вызывает put() до Thread-2. Потому что BlockingList ожидает получить 10.0, а не 9.0, он будет блокироваться на Thread-1 до Thread-2 заканчивается и звонит put(). Однажды Thread-2 дает BlockingList10.0, это notify() s все ожидающие темы, и ожидает 9.0 next. Это продолжается до тех пор, пока BlockingList не получит все ожидаемые значения.

(прошу прощения, если это было трудно следовать, если вам нужно больше разъяснений, просто спросить.)

Как и следовало ожидать из названия вопроса, есть состояние гонки здесь. Если я запустил его без каких-либо System.out.println s, он будет иногда блокироваться из-за конфликта wait() и notifyAll() s, но если я поместил println, он будет работать отлично.

Небольшая реализация этого приведен ниже, и имеет такое же поведение:

import java.math.BigDecimal; 
import java.util.concurrent.ConcurrentLinkedQueue; 

public class Example { 
    public static void main(String[] args) throws InterruptedException { 
     // Various scaling values, determined based on the graph size 
     // in the real implementation 
     BigDecimal xMax = new BigDecimal(10); 
     BigDecimal xStep = new BigDecimal(0.05); 

     // Construct the values list, from -10 to 10 
     final ConcurrentLinkedQueue<BigDecimal> values = new ConcurrentLinkedQueue<BigDecimal>(); 

     for (BigDecimal i = new BigDecimal(-10); i.compareTo(xMax) <= 0; i = i.add(xStep)) { 
      values.add(i); 
     } 

     // Contains the calculated values 
     final BlockingList list = new BlockingList(values); 

     for (int i = 0; i < 4; i++) { 
      new Thread() { 
       public void run() { 
        BigDecimal x; 

        // Keep looping until there are no more values 
        while ((x = values.poll()) != null) { 
         PointPair pair = new PointPair(); 
         pair.realX = x; 

         try { 
          list.put(pair); 
         } catch (Exception ex) { 
          ex.printStackTrace(); 
         } 
        } 
       } 
      }.start(); 
     } 
    } 

    private static class PointPair { 
     public BigDecimal realX; 
    } 

    private static class BlockingList { 
     private final ConcurrentLinkedQueue<BigDecimal> _values; 
     private final ConcurrentLinkedQueue<PointPair> _list = new ConcurrentLinkedQueue<PointPair>(); 

     public BlockingList(ConcurrentLinkedQueue<BigDecimal> expectedValues) throws InterruptedException { 
      // Copy the values into a new queue 
      BigDecimal[] arr = expectedValues.toArray(new BigDecimal[0]); 

      _values = new ConcurrentLinkedQueue<BigDecimal>(); 
      for (BigDecimal dec : arr) { 
       _values.add(dec); 
      } 
     } 

     public void put(PointPair item) throws InterruptedException { 
      while (item.realX.compareTo(_values.peek()) != 0) { 
       synchronized (this) { 
        // Block until someone enters the next desired value 
        wait(); 
       } 
      } 

      _list.add(item); 
      _values.poll(); 

      synchronized (this) { 
       notifyAll(); 
      } 
     } 
    } 
} 

Мой вопрос может кто-нибудь помочь мне найти ошибку потоковой?

Спасибо!

+4

Если вы точно знаете, сколько значений что будет, и в каком порядке, почему бы просто не выделить массив соответствующего размера, и чтобы каждый поток записывал значения в правильные точки? Никаких блокировок, никаких условий гонки ... – tzaman

+1

А ... Ну, да, это было бы значительно лучше. Фу, есть причина, по которой вы не кодируете, когда вы устали. Спасибо за предложение, я его реализую и посмотрю, как это работает. – mgbowen

+1

Ваше решение отлично подойдет, и оно работает намного быстрее! Большое спасибо! – mgbowen

ответ

5

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

Вы пишете:

Я понял, что приказывать каждую точку, как расчетный будет работать лучше

Оценим затраты производительности сделать это.

Добавление одной точки в уже отсортированный список можно выполнить очень быстро.

С алгоритмом BubbleSort это займет время O (N) - линейное время, пропорциональное размеру уже имеющегося списка. Повторение этого для каждой добавляемой точки означает, что вы повторяете эту операцию O (N) N раз, предоставляя O (N^2) производительность. Запуск этого по многим ядрам может сократить время настенных часов, но убийца - это производительность N^2.

К сожалению, Java не использует алгоритм BubbleSort внутри - он использует QuickSort, который, как правило, лучше себя ведет. Для добавления одного номера в уже отсортированный список QuickSort занимает O (N lg N); повторяя это N раз, получаем O (N^2 lg N). Двойной оуч.

Прибегая список много раз неэффективно - если вы вместо того, чтобы просто собрали все элементы вместе и сортируют их раз в конце концов, сортировка будет стоить вам всего O (N Л.Г. N) времени, что дает вам гораздо лучше конец результат.

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

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

Вместо того, чтобы использовать HashMap, попробуйте использовать простой список для Собирайте точки вместе - добавление точки в список является почти постоянным, почти нулевым временем, если вы заранее установите скорость списка.

+0

Действительно, использование массива работало намного лучше, так как я уже знаю, сколько очков у меня будет, и в каком порядке они будут. Я отмечу это как ответ, если тзаман не захочет ответить ему. Благодаря! – mgbowen

0

Это работает, если вы поставили синхронизированный перед всем методом BlockingList.put?

Что делать, если поток А выполняет

while (item.realX.compareTo(_values.peek()) != 0) { 

Thread B идет и делает

_list.add(item); 
_values.poll(); 

synchronized (this) { 
    notifyAll(); 
} 

Поток А делает

synchronized (this) { 
    // Block until someone enters the next desired value 
    wait(); 
} 
+0

Нет, к сожалению, но см. Комментарии к вопросу, я использовал решение tzaman для этой проблемы, что намного лучше, чем мой запутанный. – mgbowen

+0

обновил ответ. Как вы думаете? (Даже если вы обновились до другого решения, проблема с состоянием гонки по-прежнему остается актуальным) – aioobe

+0

Да, похоже, что это будет условие гонки, Thread B называет 'notifyAll()' перед тем, как поток A может вызвать 'wait() '. Я полагаю, что это 'wait (10)' или какое-то другое произвольное число будет работать лучше, так что он будет проверяться после бит. – mgbowen

2

Вы должны понять, почему Java требует wait() и notify() для синхронизации.

Представьте, что вы - нить, и только что закончили это.

while (item.realX.compareTo(_values.peek()) != 0) { 
      synchronized (this) { 
       // Block until someone enters the next desired value 
       wait(); 
      } 
     } 

У вас был замок, выпущенный для ожидания, а затем верните его, когда ожидание закончится. Тогда - ты снова сдался!

 _list.add(item); 
     _values.poll(); 

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

 synchronized (this) { 
      notifyAll(); 
     } 

точка «синхронизируется» для вас, чтобы сохранить замок в то время как вы делаете изменения - вы должны поставить утверждения «опрос» «ждать», «добавить» и в же " синхронизированный ".

0

Лучший способ понять условие гонки в резьбе и как их решить, вы должны прочитать эту книгу:

Java Параллелизм на практике (2006)

 Смежные вопросы

  • Нет связанных вопросов^_^