#java #thread-safety #arraylist #synchronized
#java #безопасность потоков #список массивов #синхронизировано
Вопрос:
еще раз вопрос о ArrayList и synchronize.
Я просто хотел бы знать, что именно делает этот фрагмент:
ArrayList<ObjectX> list = ....;
synchronized (list) {
if (list.contains(objectxy) == false) {
list.add(objectxy);
}
}
У меня есть список массивов, заполненный ObjectXs. Затем я хочу добавить элемент в список, но только в том случае, если список не содержит одного и того же элемента. Я проверял ранее (другим методом), содержал ли список объект — результат был отрицательным. Но возможно, что два потока одновременно думают, что результат равен no, и что они оба пытаются затем добавить objectxy. (есть некоторые другие вещи, которые необходимо выполнить между ними, вот почему я не могу синхронизировать весь процесс)
Итак, после процесса и когда теперь потоки переходят к приведенному выше фрагменту, я хочу предотвратить, чтобы эти два добавляли объект в список. Итак, я подумал, что когда я синхронизирую доступ к списку, только один поток может проверить, содержит ли он объект, а затем добавить его. После этого второй поток может получить доступ к списку, увидеть, что объект уже в нем, и больше не добавлять его.
Это то, чего я хочу достичь. Будет ли это работать? 🙂
Итак, если да, я хотел бы знать, что именно делает фрагмент. Предотвращает ли это одновременный доступ двух потоков к этому точному коду? чтобы код был доступен только для одного потока одновременно?
Или это блокирует сам список на все время для любого потока в приложении, который в данный момент пытается получить доступ к списку — где угодно? (У меня нет других функций add () в моем коде, но многие gets () , вот почему я хотел бы знать, могут ли другие потоки получить доступ к списку и по-прежнему получать элементы, пока другой поток обращается к приведенному выше коду).
Сам ArrayList является переменной-членом, которая связана с принципалом с помощью приложения. Правильно, что несколько разных потоков могут обращаться к приведенному выше коду одновременно, если они не отправляются от одного и того же участника, правильно?
Итак, вот что я хотел бы знать. Я попытался пометить свои вопросы, чтобы на них было легче отвечать. Спасибо за помощь! 🙂
[РЕДАКТИРОВАТЬ] Спасибо за все ответы, в которых почти во всех говорилось одно и то же! Я думаю, теперь это ясно! 🙂
- к синхронизированному блоку кода может быть доступен только один из основных потоков. (потоки других участников не имеют отношения к конкретному участнику). к самому списку можно получить доступ в любое время из других потоков — при условии, что доступ к нему также не синхронизирован с синхронизирующим блоком. Если это так, поток должен подождать, пока он не сможет получить доступ к списку (это означает, что ни один другой поток одновременно не находится в синхронизированном блоке)
правильно? Я надеюсь на это 🙂
Ответ №1:
В значительной степени у вас это есть. synchronized
Предотвращает одновременное выполнение своих блоков кода другими потоками, которые блокируют один и тот же list
объект. Это не блокирует list
сам объект. Другие потоки все еще могут получить к нему доступ, если они также не синхронизируются с одним и тем же объектом.
Ответ №2:
Синхронизированный блок гарантирует, что только один поток может выполнить этот блок кода или любой другой блок кода, который синхронизирован с одним и тем же объектом (т. е. списком) одновременно. Например, если у вас есть
synchronized (list) {
// block A
}
synchronized (list) {
// block B
}
тогда, если один поток выполняет блок A, никакой другой поток не может выполнять блок A или блок B, потому что они оба синхронизированы с одним и тем же объектом. Но сам список не заблокирован. Другой поток может получить доступ к списку.
Ответ №3:
Да, только один поток может получить доступ к этому блоку кода одновременно. Все остальные потоки будут ждать, пока поток, попавший туда первым, завершит выполнение блока кода.
Также ваше предположение относительно user principal верно. Если ваш список массивов равен одному для каждого пользователя (принципала), то только потоки, выполняемые с этим пользователем (принципалом), должны будут синхронизироваться с этим конкретным списком массивов.
Ответ №4:
Помимо согласия с тем, что, как и в других ответах, предполагается, что он будет блокировать ТОЛЬКО один и тот же блок кода. Я думаю, что ключевая путаница, которая у вас есть, и у большинства людей, связана с блокировкой в synchronized (блокировка). В вашем случае вы использовали сам список в качестве блокировки. Однако, использование объекта в качестве блокировки и будет ли заблокирован код в объекте, совершенно не имеют значения. Фактически, вы можете использовать любой объект в качестве блокировки, если это один и тот же объект. Это означает, что если у вас есть другая переменная-член с именем foo, приведенный ниже код будет выполняться практически так же:
synchronized (foo) {
if (list.contains(objectxy) == false) {
list.add(objectxy);
}
}
Каждый объект может быть использован в качестве блокировки.
Ответ №5:
Это будет работать до тех пор, пока все потоки синхронизируются с объектом, прежде чем что-либо делать. Обычной практикой является скрытие списка из других и предоставление копии только для чтения.
public class ThreadSafeList {
private ArrayList<String> list = new ArrayList<String>();
public synchronized void addUnique(String s) {
if (!list.contains(s)) {
list.add(s);
}
}
public synchronized List<String> getList() {
return Collections.unmodifiableList((new ArrayList<String>(list)));
}
}
Синхронизация гарантируется инкапсуляцией.
Синхронизированный метод похож на
public void addUnique(String s)
synchronized(this){
list.add(s);
}
И для java нет разницы в том, что вы синхронизируете, но безопаснее иметь отдельный объект блокировки.
Ответ №6:
Вы также можете использовать вектор (синхронизированный список) или другие синхронизированные коллекции. Если вы выполняете много операций чтения, но меньше операций записи, вы могли бы использовать CopyOnWriteArrayList.
Но если вы часто выполняете записи, это приводит к большим накладным расходам.
Комментарии:
1. спасибо за ваш ответ. Я не использую synchronizedList, потому что у меня очень, очень мало операций над ним, которые требуют синхронизации — вот почему я думаю, что это не обязательно. Поскольку CopyOnWriteArrayList также потокобезопасен, я думаю, что это было бы слишком для моего приложения — мне действительно не нужна синхронизация в каждой точке. 🙂
2. Будьте очень осторожны с этим подходом: вы должны быть уверены, что каждый доступ к списку не будет перекрываться никаким другим доступом. Например, если есть вероятность, что один из ваших потоков выполняет get(), в то время как другой выполняет инструкцию synchronized(list) { add() / set() }, вы должны синхронизировать также инструкцию get().
3. почему? если это только потому, что пользователь мог получить данные, которые не обновлены (возможно, отсутствует одно добавление), то это не важно — по крайней мере, не для моего приложения, я имею в виду 🙂 Я могу с этим смириться
4. Обновление — довольно неуловимая концепция в многопоточных приложениях, так что проблема не в этом. Основная проблема заключается в том, что время от времени могут возникать случайные исключения, в зависимости от того, как реализована структура данных. Например, ArrayList внутренне использует объект[] для хранения данных; когда этот объект [] заполнен, класс ArrayList создает новый, больший по размеру, и копирует данные. Итак, если вы выполняете несинхронизированный get(), в то время как другой поток добавляет () и изменяет внутренний размер массива, результатом get () может быть correct, null, исключение NullPointerException…
5. интересно. Тогда CopyOnWriteArrayList (потокобезопасный) или другой synchronizedList решает эту проблему. Спасибо за объяснение, теперь я понимаю! Как насчет того, чтобы каждый раз проверять, заполнен ли список массивов, и если это так, использовать synchronize для расширения списка массивов? (и, надеюсь, список массивов всегда достаточно большой, и это случается не часто)