Я создаю графический калькулятор.Исключительное состояние гонки в 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
дает BlockingList
10.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();
}
}
}
}
Мой вопрос может кто-нибудь помочь мне найти ошибку потоковой?
Спасибо!
Если вы точно знаете, сколько значений что будет, и в каком порядке, почему бы просто не выделить массив соответствующего размера, и чтобы каждый поток записывал значения в правильные точки? Никаких блокировок, никаких условий гонки ... – tzaman
А ... Ну, да, это было бы значительно лучше. Фу, есть причина, по которой вы не кодируете, когда вы устали. Спасибо за предложение, я его реализую и посмотрю, как это работает. – mgbowen
Ваше решение отлично подойдет, и оно работает намного быстрее! Большое спасибо! – mgbowen