Наиболее распространенная проблема параллелизма, которую я видел, не понимает, что поле, записанное одним потоком, не гарантировано , чтобы быть замеченным различным потоком. Распространенное приложение этого:
class MyThread extends Thread {
private boolean stop = false;
public void run() {
while(!stop) {
doSomeWork();
}
}
public void setStop() {
this.stop = true;
}
}
, пока остановка не энергозависима или setStop
, и run
не , синхронизировался , это, как гарантируют, не будет работать. Эта ошибка является особенно дьявольской как в 99,999%, она не будет иметь значения на практике, поскольку поток читателя будет в конечном счете видеть изменение - но мы не знаем, как скоро он видел его.
Использование глобального объекта, такого как статическая переменная для блокировки.
Это приводит к очень плохой производительности из-за конкуренции.
Honesly? До появления java.util.concurrent
, наиболее распространенная проблема, с которой я обычно сталкивался, была тем, что я называю "перегрузкой потока": Приложения, которые используют потоки для параллелизма, но порождают слишком многих из них и заканчивают тем, что перегрузились.
Не понимание, что this
во внутреннем классе не this
из внешнего класса. Обычно в анонимном внутреннем классе, который реализует Runnable
. Корневая проблема состоит в том, что, потому что синхронизация является частью всего Object
, с там не является эффективно никакой статической проверкой типа. Я видел это, по крайней мере, дважды в Usenet, и это также появляется в Brian Goetz'z Java Concurrency на практике.
закрытия BGGA не страдают от этого, поскольку нет никакого this
для закрытия (this
ссылки внешний класс). Если Вы используете не - this
объекты как блокировки тогда, это обходит эту проблему и других.
Несколько объектов, которые являются защищенной блокировкой, но обычно получаются доступ по очереди. Мы столкнулись с несколькими случаями, где блокировки получены различным кодом в различных заказах, приводящих к мертвой блокировке.
Другая общая проблема 'параллелизма' должна использовать синхронизируемый код, когда это не необходимо вообще. Например, я все еще вижу, что программисты используют StringBuffer
или даже java.util.Vector
(как локальные переменные метода).
Пока я не посещал урок с Brian Goetz, я не понял, что несинхронизируемый getter
из частного поля, видоизмененного через синхронизируемый setter
, никогда , гарантировал, что возвратил обновленное значение. Только, когда переменная защищена синхронизируемым блоком на и чтения И записи будут Вы получать гарантию последнего значения переменной.
public class SomeClass{
private Integer thing = 1;
public synchronized void setThing(Integer thing)
this.thing = thing;
}
/**
* This may return 1 forever and ever no matter what is set
* because the read is not synched
*/
public Integer getThing(){
return thing;
}
}
Типичная проблема использует классы как Календарь и SimpleDateFormat от нескольких потоков (часто путем кэширования их в статической переменной) без синхронизации. Эти классы не ориентированы на многопотоковое исполнение, таким образом, многопоточный доступ в конечном счете вызовет странные проблемы с непоследовательным состоянием.
Перепроверяемая Блокировка. В общем и целом.
парадигма, которая я начал изучать проблемы того, когда я работал в BEA, то, что люди проверят одиночный элемент следующим образом:
public Class MySingleton {
private static MySingleton s_instance;
public static MySingleton getInstance() {
if(s_instance == null) {
synchronized(MySingleton.class) { s_instance = new MySingleton(); }
}
return s_instance;
}
}
Это никогда не работает, потому что другой поток, возможно, вошел в синхронизируемый блок, и s_instance больше не является пустым. Таким образом, естественное изменение должно тогда сделать его:
public static MySingleton getInstance() {
if(s_instance == null) {
synchronized(MySingleton.class) {
if(s_instance == null) s_instance = new MySingleton();
}
}
return s_instance;
}
, Который не работает также, потому что Модель памяти Java не поддерживает его. Необходимо объявить, что s_instance как энергозависимый заставляет его работать, и даже тогда это только работает над Java 5.
Люди, которые не знакомы с запутанностью Модели памяти Java, смешивают это все время .
Не правильно синхронизация на объектах, возвращенных Collections.synchronizedXXX()
, особенно во время повторения или нескольких операций:
Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
...
if(!map.containsKey("foo"))
map.put("foo", "bar");
Это неправильно . Несмотря на единственные операции, являющиеся synchronized
, состояние карты между вызовом contains
и put
может быть изменено другим потоком. Это должно быть:
synchronized(map) {
if(!map.containsKey("foo"))
map.put("foo", "bar");
}
Или с ConcurrentMap
реализация:
map.putIfAbsent("foo", "bar");
Хотя, вероятно, не точно, что Вы просите, самая частая связанная с параллелизмом проблема, с которой я встретился (вероятно, потому что она подходит в нормальном однопоточном коде)
java.util.ConcurrentModificationException
вызваны вещами как:
List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
for (String string : list) { list.remove(string); }
Наиболее распространенная ошибка, которую мы видим, где я работаю, является программистами, выполняют долгие операции, как вызовы сервера, на EDT, запирая GUI в течение нескольких секунд и делая приложение безразличным.
Упущение ожидать () (или Condition.await ()) в цикле, проверяя, что условие ожидания на самом деле верно. Без этого Вы сталкиваетесь с ошибками от побочного ожидания () пробуждения. Каноническое использование должно быть:
synchronized (obj) {
while (<condition does not hold>) {
obj.wait();
}
// do stuff based on condition being true
}
Может быть легко думать, синхронизировался, наборы предоставляют Вам больше защиты, чем они на самом деле делают и забывают держать блокировку между вызовами. Я видел эту ошибку несколько раз:
List<String> l = Collections.synchronizedList(new ArrayList<String>());
String[] s = l.toArray(new String[l.size()]);
, Например, во второй строке выше, toArray()
и size()
методы и ориентированы на многопотоковое исполнение самостоятельно, но эти size()
оценен отдельно от эти toArray()
, и блокировка в Списке не сохранена между этими двумя вызовами.
при выполнении этого кода с другим потоком одновременно объекты удаления из списка, рано или поздно Вы закончите с новым String[]
, возвратился, который больше, чем необходимый для содержания всех элементов в списке и имеет нулевые значения в хвосте. Легко думать что, потому что эти два вызова метода Списка происходят в одной строке кода, это - так или иначе атомарная операция, но это не.
Другая общая ошибка является плохой обработкой исключений. Когда фоновый поток выдает исключение, если Вы не обрабатываете его правильно, Вы не могли бы видеть отслеживание стека вообще. Или возможно Ваша фоновая задача прекращает работать и никогда не запускается снова, потому что Вам не удалось обработать исключение.
Используя локальный "новый Объект ()" как взаимное исключение.
synchronized (new Object())
{
System.out.println("sdfs");
}
Это бесполезно.
Думая Вы пишете однопоточный код, но используете изменяемые помехи (включая одиночные элементы). Очевидно, они будут совместно использованы потоками. Это происходит удивительно часто.
Произвольные вызовы метода не должны быть сделаны из синхронизируемых блоков.
Dave Ray, тронутый этого в его первом ответе, и на самом деле, я также встретился с мертвой блокировкой, также имеющей отношение к вызову методов на слушателях из синхронизированного метода. Я думаю, что более общий урок - то, что вызовы метода не должны быть превращены "в дикую местность" из синхронизируемого блока - Вы понятия не имеете, будет ли вызов продолжителен, результат в мертвой блокировке, или что бы то ни было.
В этом случае, и обычно в целом, решение состояло в том, чтобы уменьшить объем синхронизируемого блока, чтобы просто защитить критическое частный раздел кода.
кроме того, так как мы теперь получали доступ к Набору слушателей за пределами синхронизируемого блока, мы изменили его, чтобы быть Набором копии на записи. Или мы, возможно, просто сделали защитную копию Набора. Точка быть, обычно существуют альтернативы для безопасного доступа к Набору неизвестных объектов.
Новая Связанная с параллелизмом ошибка, с которой я столкнулся, была объектом, который в его конструкторе создал ExecutorService, но когда на объект больше не ссылались, это, никогда не имело не завершают работу ExecutorService. Таким образом, в течение недель, тысячи из пропущенных потоков, в конечном счете заставляя систему отказать. (Технически, это не отказало, но это действительно прекращало функционировать правильно, продолжая работать.)
Технически, я предполагаю, что это не проблема параллелизма, но это - проблема, имеющая отношение к использованию java.util.concurrency библиотек.
Я встретился с проблемой параллелизма с Сервлетами, когда существуют изменяемые поля, которые будут setted каждым запросом. Но существует только один экземпляр сервлета для всего запроса, таким образом, это работало отлично в среде отдельного пользователя, но когда больше чем один пользователь запросил сервлет произошли, непредсказуемые результаты.
public class MyServlet implements Servlet{
private Object something;
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException{
this.something = request.getAttribute("something");
doSomething();
}
private void doSomething(){
this.something ...
}
}
Моей самой большой проблемой всегда были мертвые блокировки, особенно вызванные слушателями, которые уволены с сохраненной блокировкой. В этих случаях действительно легко получить инвертированную блокировку между двумя потоками. В моем случае, между моделированием, работающим в одном потоке и визуализацией моделирования, работающего в потоке UI.
РЕДАКТИРОВАНИЕ: Перемещенная вторая часть для разделения ответа.
Несбалансированная синхронизация, особенно против Карт, кажется, довольно типичная проблема. Многие люди полагают, что синхронизация на помещает в Карту (не ConcurrentMap, но скажите HashMap), и не синхронизирующийся на добирается, достаточно. Это однако может привести к бесконечному циклу во время рехеширования.
та же проблема (частичная синхронизация) может произойти где угодно, у Вас есть общее состояние с чтениями и записями как бы то ни было.
Не точно ошибка, но, худший грех обеспечивает библиотеку, которой Вы предназначаете других людей для использования, но не утверждение, какие классы/методы ориентированы на многопотоковое исполнение и которые нужно только назвать от единственного потока и т.д.
[еще 111], люди должны использовать аннотации параллелизма (например, @ThreadSafe, @GuardedBy и т.д.) описанный в книге Goetz.
Запуск потока в конструкторе из класса проблематичен. Если класс расширяется, поток может быть запущен , прежде чем конструктор подкласса будет выполнен.
Изменяемые классы в структурах совместно используемых данных
Thread1:
Person p = new Person("John");
sharedMap.put("Key", p);
assert(p.getName().equals("John"); // sometimes passes, sometimes fails
Thread2:
Person p = sharedMap.get("Key");
p.setName("Alfonso");
, Когда это происходит, код, намного более сложны что этот упрощенный пример. Тиражирование, нахождение и исправление ошибки трудны. Возможно, этого можно было избежать, если мы могли бы отметить определенные классы как неизменные и определенные структуры данных как только содержание неизменных объектов.
Я верю в будущее, которым основная проблема с Java будет (отсутствие) гарантии видимости конструкторов. Например, если Вы создаете следующий класс
class MyClass {
public int a = 1;
}
и затем просто читаете свойство MyClass от другого потока, MyClass.a мог быть или 0 или 1, в зависимости от реализации и настроения JavaVM. Сегодня возможности для существа 1 очень высоки. Но на будущих машинах NUMA это может отличаться. Многие люди не знают об этом и полагают, что они не должны заботиться о многопоточности во время фазы инициализации.
Синхронизация на строковом литерале или постоянный определенный строковым литералом является (потенциально) проблемой, поскольку строковый литерал интернируется и будет совместно использован кем-либо еще в JVM с помощью того же строкового литерала. Я знаю, что эта проблема подошла в серверах приложений и других "контейнерных" сценариях.
Пример:
private static final String SOMETHING = "foo";
synchronized(SOMETHING) {
//
}
В этом случае, любой использующий строку "нечто" для соединений совместно использует ту же блокировку.
Самая немая ошибка, которую я часто делаю, забывает синхронизироваться перед вызовом уведомляют () или ожидают () на объекте.
Одна классическая проблема изменяет объект, на котором Вы синхронизируетесь при синхронизации на ней:
synchronized(foo) {
foo = ...
}
Другие параллельные потоки тогда синхронизируются на различном объекте, и этот блок не обеспечивает взаимное исключение, которое Вы ожидаете.
Мой #1 самый болезненный проблема параллелизма когда-либо происходила, когда два различных библиотеки с открытым исходным кодом сделали что-то вроде этого:
private static final String LOCK = "LOCK"; // use matching strings
// in two different libraries
public doSomestuff() {
synchronized(LOCK) {
this.work();
}
}
На первый взгляд, это похоже на довольно тривиальный пример синхронизации. Однако; потому что Строки , интернировал в Java, литеральная строка "LOCK"
оказывается тем же экземпляром java.lang.String
(даже при том, что они объявляются полностью разрозненным образом друг от друга.) Результат очевидно плох.