Здравствуйте, rsn81, Вы писали:
R>Здравствуйте, dshe, Вы писали:
D>>Откуда известно, что производительность java.util.concurrent.locks.ReentrantLock выше, чем у стандатного synchronized? R>От Гетца.
13.4. Choosing Between Synchronized and ReentrantLock (Java Concurrency in Practice)
ReentrantLock provides the same locking and memory semantics as intrinsic locking, as well as additional features such as timed lock waits, interruptible lock waits, fairness, and the ability to implement non-block-structured locking. The performance of ReentrantLock appears to dominate that of intrinsic locking, winning slightly on Java 6 and dramatically on Java 5.0.
...
Во как! Но читаем далее
...
ReentrantLock is an advanced tool for situations where intrinsic locking is not practical. Use it if you need its advanced features: timed, polled, or interruptible lock acquisition, fair queueing, or non-block-structured locking. Otherwise, prefer synchronized.
...
И наконец (последний абзац следовало бы выделить целиком)
...
Future performance improvements are likely to favor synchronized over ReentrantLock. Because synchronized is built into the JVM, it can perform optimizations such as lock elision for thread-confined lock objects and lock coarsening to eliminate synchronization with intrinsic locks (see Section 11.3.2); doing this with library-based locks seems far less likely. Unless you are deploying on Java 5.0 for the foreseeable future and you have a demonstrated need for ReentrantLock's scalability benefits on that platform, it is not a good idea to choose ReentrantLock over synchronized for performance reasons.
Из минусов ReentrantLock, пожалуй, можно привести только то, что он требует большей ответственности при программировании: не забывать unlock в блоке finally — с synchronized это делает за вас виртуальная машина. Ну а основной аргумент, почему не стоит отказываться от synchronized — это преемственность (про synchronized знает любой относительно не новичок в Java, новыми возможностями платформы интересуются в основном только профессионалы), большая функциональная поддержка со стороны виртуальной машины, ну и ожидания, что в новых версиях JVM synchronized будет улучшен. Гетц четко и говорит, цитирую:
Классы блокировки в java.util.compatible.lock — это передовой инструментарий для продвинутых пользователей и сложных ситуаций.
Ну и самое главное, претензию вашу не очень понял: выше я вовсе не утверждал, что нужно железно заменить synchronized на ReentrantLock — а только привел альтернативный вариант кода, ну и упомянул, что он в общем случае более производительный. Вроде бы нигде не соврал.
public class Channel {
private Object mutexCurrent = new Object();
private List current;
public String build() {
// здесь нужна блокировка на current
synchronized(mutexCurrent) {
current.clear();
current.add(some...);
}
// здесь уже не нужна на current
}
public List getCurrent() {
List ret = null;
synchronized(mutexCurrent) {
ret = new ArrayList(current);
}
return ret;
}
}
public class ChannelMixer {
private Channel channel;
public synchronized String build() {
List selected = new ArrayList();
selected.addAll(channel.getCurrent()); // здесь нужно дождаться пока завершиться модификация current, и только затем его прочитать
}
}
Поправьте меня, если я не прав. В этом случае Channel#getCurrent() вернёт список инстансов класса "some...", которые содержал Channel#current, на момент выполнения Channel#getCurrent(). Но само состояние инстансов "some..." может быть изменено в других потоках. Если хочешь 100% не изменённый снимок получить, используй клонирование внутри:
public List getCurrent() {
List ret = null;
synchronized(mutexCurrent) {
ret = clone(current);
}
return ret;
}
F>Кто-то может вызвать Channel#build(), в свою очередь параллельно может быть запущен ChannelMixer#build(). Мне нужно, чтобы во время Channel#build(), на месте где происходит модификация Channel#current выставлялась блокировка на это поле, чтобы никто другой из вне не мог читать это поле (т.е. ожидал пока завершится модификация current в build()).
Здравствуйте, Foror, Вы писали:
F>Кто-то может вызвать Channel#build(), в свою очередь параллельно может быть запущен ChannelMixer#build(). Мне нужно, чтобы во время Channel#build(), на месте где происходит модификация Channel#current выставлялась блокировка на это поле, чтобы никто другой из вне не мог читать это поле (т.е. ожидал пока завершится модификация current в build()).
F>Собственно как сделать блокировку на current?
Блокировка это, конечно, хорошо, но её придётся делать почти везде где происходит вызов getCurrent().
Не силён в многопоточности, так что, пользуясь случаем хотелось бы задать вопрос знатокам...
Если на самом деле не нужно ожидать завершения модификации, можно ли написать так?
public class Channel {
private List current;
public String build() {
List nc = new ArrayList();
nc.add(some...);
current = nc;
}
public List getCurrent() {
return current;
}
}
R>читающий поток будет получать актуальное состояние коллекции на момент запроса — по описание не очень понял, устроит такое поведение или нет.
Нет. Пока выполняется добавления/удаления в current, другие должны ждать завершения всех этих операций.
R>Другой простой, но вроде именно ваш вариант — использовать блокировку кода на основе java.util.concurrent.locks.ReentrantLock примерно. Суть та же, что и стандартная блокировка (synchronized) по изменяемому объекту (current), но производительность выше.
Почему это быстрее, и даже если это быстрее почему бы компилятору не сделать оптимизацию под этот вариант? У меня сейчас так: synchronized(current) {current.add(some...);}
R>} И потом, самое главное — это избавить клиентский код от необходимости блокировки: потокобезопасные классы должны по умолчанию предоставлять безопасный код, то есть не требующий дополнительных проверок на вызывающей стороне.
Здравствуйте, rsn81, Вы писали:
R>...Ну и самое главное, претензию вашу не очень понял: выше я вовсе не утверждал, что нужно железно заменить synchronized на ReentrantLock — а только привел альтернативный вариант кода, ну и упомянул, что он в общем случае более производительный. Вроде бы нигде не соврал.
Мне показалось спорным утверждение о том, что ReentrantLock более производительней, чем старый добрый synchronized. Это заставило меня порыться в Интернете и я кое-что для себя выяснил. За это вам отдельное спасибо. Признаюсь, я был удивлен обнаружив подтверждения тому, что противоречило моим представлениям. Я пока не вижу принципиальных причин почему synchronized должен быть медленнее ReentrantLock (ведь в крайнем случае JVM могла бы неявно использовать RL в качестве реализации synchronized). И то, что synchronized в Java 5 работает медленнее мне кажется не более чем досадным недоразумением. Еще раз спасибо за ссылки.
public class Channel {
private List current;
public synchronized String build() {
// здесь нужна блокировка на current
current.clear();
current.add(some...);
// здесь уже не нужна на current
}
public List getCurrent() {
return current;
}
}
public class ChannelMixer {
private Channel channel;
public synchronized String build() {
List selected = new ArrayList();
selected.addAll(channel.getCurrent()); // здесь нужно дождаться пока завершиться модификация current, и только затем его прочитать
}
}
Кто-то может вызвать Channel#build(), в свою очередь параллельно может быть запущен ChannelMixer#build(). Мне нужно, чтобы во время Channel#build(), на месте где происходит модификация Channel#current выставлялась блокировка на это поле, чтобы никто другой из вне не мог читать это поле (т.е. ожидал пока завершится модификация current в build()).
Здравствуйте, Георгий, Вы писали:
Г>Здравствуйте, ingie, Вы писали:
I>>Блокировка это, конечно, хорошо, но её придётся делать почти везде где происходит вызов getCurrent().
Г>Речь шла о блокировке на current. В том числе, в методе getCurrent().
Блокировка в getCurrent() не поможет, если где-то будет написано
c = getCurrent();
doSome(c);
Да и вообще она почти ничему не поможет.
Если уж с блокировками, то может лучше
class Channel {
...
forEach(Visitor v) {
synchronized(current) {
for (o : current) {
v.visit(o);
}
}
}
...
}
И без getCurrent() вообще.
Иначе не будет гарантий как я понимаю. Или нет?
I>>Если на самом деле не нужно ...
Г>Вопрос в том, является ли присвоение атомарной операцией.
В current может лежать только старое значение или новое значение, никаких промежуточных вариантов быть не может,
как я понимаю. Допустим меня устраивают оба варианта.
I>Блокировка в getCurrent() не поможет, если где-то будет написано I>
I>c = getCurrent();
I>doSome(c);
I>
I>Да и вообще она почти ничему не поможет.
Чтобы нельзя было сделать doSome(c), нужно, чтобы с было Immutable, неизменяемое. В данном случае, List можно возвращать
через Collections.unmodifiableList(list).
K>Чтобы нельзя было сделать doSome(c), нужно, чтобы с было Immutable, неизменяемое. В данном случае, List можно возвращать K>через Collections.unmodifiableList(list).
Цель не будет достигнута. Так как:
UnmodifiableCollection(Collection<? extends E> c) {
if (c==null)
throw new NullPointerException();
this.c = c;
}
public Iterator<E> iterator() {
return new Iterator<E>() {
Iterator<? extends E> i = c.iterator();
public boolean hasNext() {return i.hasNext();}
public E next() {return i.next();}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
Тогда уж надо
synchronized (current) {
a = new ArrayList();
a.addAll(current);
return a;
}
I>>И без getCurrent() вообще. I>>Иначе не будет гарантий как я понимаю. Или нет?
K>А зачем такие сложнотсти? Загромоздите архитектуру..
Действительно, зачем? Я вообще-то сразу предложил обойтись без блокировок, только не знаю правильно — нет.
Здравствуйте, ingie, Вы писали:
I>Действительно, зачем? Я вообще-то сразу предложил обойтись без блокировок, только не знаю правильно — нет.
Правильно то что, если можно обойтись, то лучше обойтись.
Здравствуйте, Георгий, Вы писали:
Г>Здравствуйте, Blazkowicz, Вы писали:
Г>>>Вопрос в том, является ли присвоение атомарной операцией.
B>>И на что эта атомарность влияет?
Г>Ни на что не влияет. Опечатка в посте. Должно было быть так: Г>
Г>Вопрос в том, является ли присвоение атомарной операцией?
Простой ответ:
Строчка i = i;
1. i пихается в стек
2. параллельный тред изменяет i
3. в i пихается значение из стека
[skipped] F>Собственно как сделать блокировку на current?
Самое забавное, что по-вашему коду не видно, какая реализация интерфейса java.util.List используется. Собственно приведенный код вообще будет всегда материться посредством java.lang.NullPointerException. А это ключевой момент. Если, к примеру, в качестве реализации списка взять java.util.concurrent.CopyOnWriteArrayList, то блокировки изменения, возможно (!), вам будут и вовсе не нужны: читающий поток будет получать актуальное состояние коллекции на момент запроса — по описание не очень понял, устроит такое поведение или нет. В JDK 1.6 стоит посмотреть и другие реализации коллекций в пакете java.util.concurrent.
Другой простой, но вроде именно ваш вариант — использовать блокировку кода на основе java.util.concurrent.locks.ReentrantLock примерно так:
public class Channel<T> {
private final ReentrantLock lockObj = new ReentrantLock();
private final List<T> current = new ArrayList<T>();
public String build() {
lockObj.lock();
try {
// действия с current
} finally {
lockObj.unlock();
}
}
public List<T> getCurrent() {
final List<T> result = null;
lockObj.lock();
try {
result = current;
} finally {
lockObj.unlock();
}
return result;
}
}
Суть та же, что и стандартная блокировка (synchronized) по изменяемому объекту (current), но производительность выше. И потом, самое главное — это избавить клиентский код от необходимости блокировки: потокобезопасные классы должны по умолчанию предоставлять безопасный код, то есть не требующий дополнительных проверок на вызывающей стороне.
Здравствуйте, rsn81, Вы писали:
R>Другой простой, но вроде именно ваш вариант — использовать блокировку кода на основе java.util.concurrent.locks.ReentrantLock примерно так:
R>public class Channel<T> {
R>...
R> public List<T> getCurrent() {
R> final List<T> result = null;
R> lockObj.lock();
R> try {
R> result = current;
R> } finally {
R> lockObj.unlock();
R> }
R> return result;
R> }
R>}
В данном примере, синхронизация в getCurrent не гарантирует, что возвращенный List<T> не будет изменен в другом потоке при последующем вызове build. Как правильно заметил RavshanKos
, имеет смысл подумать о том, чтобы вернуть копию current, а не непосредственно current.
R>Суть та же, что и стандартная блокировка (synchronized) по изменяемому объекту (current), но производительность выше.
Откуда известно, что производительность java.util.concurrent.locks.ReentrantLock выше, чем у стандатного synchronized?
Здравствуйте, dshe, Вы писали:
D>Откуда известно, что производительность java.util.concurrent.locks.ReentrantLock выше, чем у стандатного synchronized?
От Гетца.
Здравствуйте, dshe, Вы писали:
D>В данном примере, синхронизация в getCurrent не гарантирует, что возвращенный List<T> не будет изменен в другом потоке при последующем вызове build. Как правильно заметил RavshanKos
, имеет смысл подумать о том, чтобы вернуть копию current, а не непосредственно current.
Угу, не учел. Как другой вариант, можно, если это катит по спецификации на клас, вернуть неизменяемую коллекцию с помощью Collections.unmodifiableList.
Кстати, по поводу java.util.concurrent.locks.ReentrantLock: коллекция java.util.concurrent.CopyOnWriteArrayList для перераспределения (копирование части массива) коллекции во время изменения, насколько помню, использует именно эту блокировку.
Здравствуйте, rsn81, Вы писали:
R>Здравствуйте, dshe, Вы писали:
D>>В данном примере, синхронизация в getCurrent не гарантирует, что возвращенный List<T> не будет изменен в другом потоке при последующем вызове build. Как правильно заметил RavshanKos
, имеет смысл подумать о том, чтобы вернуть копию current, а не непосредственно current. R>Угу, не учел. Как другой вариант, можно, если это катит по спецификации на клас, вернуть неизменяемую коллекцию с помощью Collections.unmodifiableList.
Не думаю, что это поможет. Она ведь должна быть неизменяема внутри, а не снаружи.
Здравствуйте, dshe, Вы писали:
D>Не думаю, что это поможет. Она ведь должна быть неизменяема внутри, а не снаружи.
Быть может, видимо, не до конца понял, чего же нужно автору.
Для того чтобы класс был поточнобезопасным, прежде всего, он должен вести себя корректно в однопоточной среде. Если класс корректно реализован, что является другим способом подтверждения соответствия его спецификации, никакая последовательность операций (чтение или запись общих полей и вызовы общих методов) над объектом данного класса не должна быть в состоянии привести этот объект в неисправное состояние, наблюдать этот объект в любом неисправном состоянии, или нарушить какой-либо инвариант класса, входные или выходные условия.
Кроме того, для того, чтобы класс был поточнобезопасным, он должен продолжать вести себя корректно, как описано выше, в случае доступа из нескольких потоков, независимо от графика или интерливинга исполнения этих потоков средой выполнения, без какой-либо дополнительной синхронизации со стороны вызывающего кода. Влияние данных операций на поточнобезопасный объект проявится во всех потоках в фиксированном, глобально согласующемся порядке.
Здравствуйте, dshe, Вы писали:
D>Мне показалось спорным утверждение о том, что ReentrantLock более производительней, чем старый добрый synchronized.
Угу, само появление ReentrantLock и та статья Гетца, несмотря на все увещевания и предостережения, вообще полны противоречий: казалось бы, изобрели более качественный альтернативный инструмент, а тем не менее... Мне кажется, ReentrantLock только временно выигрывает у synchronized, дорихтуют. То есть, разумеется, проектировался просто как инструмент с более широким функционалом, нежели synchronized, но раз уж получился впридачу и более быстрым (временно), ну что ж тут поделать... не замедлять же!
D>Это заставило меня порыться в Интернете и я кое-что для себя выяснил. За это вам отдельное спасибо. Признаюсь, я был удивлен обнаружив подтверждения тому, что противоречило моим представлениям. Я пока не вижу принципиальных причин почему synchronized должен быть медленнее ReentrantLock (ведь в крайнем случае JVM могла бы неявно использовать RL в качестве реализации synchronized).
Да вроде не могла бы: ReentrantLock — обычный исполняемый байт-код, а synchronized реализован на уровне виртуальной машины (зашит в язык), за счет чего на основе synchronized виртуальная машина потенциально может осуществлять некоторые манипуляции с программой (что-то Гетц там про это писал, точно не помню).
D>И то, что synchronized в Java 5 работает медленнее мне кажется не более чем досадным недоразумением. Еще раз спасибо за ссылки.
Угу, согласен. Один нюанс только: вендоров и виртуальных машин много, а JDK — один.