В каких случаях требуется синхронный доступ к методам в Java?
В каких случаях необходимо синхронизировать доступ к членам экземпляра? Я понимаю, что доступ к статическим членам класса всегда должен быть синхронизирован, потому что они являются общими для всех экземпляров объекта класса.
У меня вопрос, когда я буду неправильным, если я не синхронизирую членов экземпляра?
например, если мой класс
public class MyClass {
private int instanceVar = 0;
public setInstanceVar()
{
instanceVar++;
}
public getInstanceVar()
{
return instanceVar;
}
}
в каких случаях (использования класса MyClass
) мне нужно иметь методы:public synchronized setInstanceVar()
а такжеpublic synchronized getInstanceVar()
?
Заранее спасибо за ваши ответы.
6 ответов
Это зависит от того, хотите ли вы, чтобы ваш класс был потокобезопасным. Большинство классов не должны быть поточно-ориентированными (для простоты), в этом случае вам не нужна синхронизация. Если вам нужно, чтобы он был потокобезопасным, вам следует синхронизировать доступ или сделать переменную volatile. (Это позволяет другим потокам не получать "устаревшие" данные.)
synchronized
Модификатор действительно плохая идея, и его следует избегать любой ценой. Я думаю, что похвально, что Sun пыталась сделать блокировку немного легче, но synchronized
просто вызывает больше проблем, чем оно того стоит.
Проблема в том, что synchronized
Метод на самом деле просто синтаксический сахар для получения блокировки this
и удерживая его на протяжении всего метода. Таким образом, public synchronized void setInstanceVar()
будет эквивалентно что-то вроде этого:
public void setInstanceVar() {
synchronized(this) {
instanceVar++;
}
}
Это плохо по двум причинам:
- Все
synchronized
методы в одном и том же классе используют точно такую же блокировку, что снижает пропускную способность - Любой может получить доступ к блокировке, включая членов других классов.
Ничто не мешает мне делать что-то подобное в другом классе:
MyClass c = new MyClass();
synchronized(c) {
...
}
В этом synchronized
Блок, я держу замок, который требуется всем synchronized
методы в MyClass
, Это дополнительно снижает пропускную способность и резко увеличивает шансы на тупик.
Лучший подход - иметь lock
объект и использовать synchronized(...)
блокировать напрямую:
public class MyClass {
private int instanceVar;
private final Object lock = new Object(); // must be final!
public void setInstanceVar() {
synchronized(lock) {
instanceVar++;
}
}
}
Кроме того, вы можете использовать java.util.concurrent.Lock
интерфейс и java.util.concurrent.locks.ReentrantLock
Реализация для достижения в основном того же результата (на самом деле, это то же самое на Java 6).
Если вы хотите сделать этот класс потокобезопасным, я бы объявил instanceVar
как volatile
чтобы убедиться, что вы всегда получаете самое последнее значение из памяти, а также я бы сделал setInstanceVar()
synchronized
потому что в JVM приращение не является атомарной операцией.
private volatile int instanceVar =0;
public synchronized setInstanceVar() { instanceVar++;
}
, Грубо говоря, ответ "это зависит". Синхронизация вашего установщика и получателя здесь будет иметь целью только гарантию того, что несколько потоков не смогут читать переменные между операциями приращения друг друга:
synchronized increment()
{
i++
}
synchronized get()
{
return i;
}
но это на самом деле даже не сработает, потому что для того, чтобы поток вызывающей стороны получил то же значение, что и приращенный, вам нужно будет гарантировать, что вы атомарно увеличиваете, а затем извлекаете, чего вы здесь не делаете - то есть вы должен был сделать что-то вроде
synchronized int {
increment
return get()
}
По сути, синхронизация полезна для определения того, какие операции должны быть гарантированно выполнены для обеспечения безопасности потоков (другими словами, вы не можете создать ситуацию, когда отдельный поток подрывает вашу операцию и ведет к тому, что ваш класс ведет себя нелогично или подрывает то, что вы ожидаете, состояние данных быть). На самом деле это большая тема, чем здесь.
Эта книга Java Concurrency in Practice превосходна и, безусловно, гораздо надежнее меня.
Проще говоря, вы используете synchronized, когда у вас есть несколько потоков, обращающихся к одному и тому же методу одного и того же экземпляра, который изменит состояние объекта / или приложения.
Он предназначен как простой способ предотвращения состязаний между потоками, и на самом деле его следует использовать только тогда, когда вы планируете, чтобы параллельные потоки обращались к одному и тому же экземпляру, такому как глобальный объект.
Теперь, когда вы читаете состояние экземпляра объекта с параллельными потоками, вы можете захотеть взглянуть на java.util.concurrent.locks.ReentrantReadWriteLock - который теоретически позволяет многим потокам читать одновременно, но только одной теме разрешено писать. Итак, в примере метода получения и установки, который, кажется, дают все, вы можете сделать следующее:
public class MyClass{
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private int myValue = 0;
public void setValue(){
rwl.writeLock().lock();
myValue++;
rwl.writeLock().unlock();
}
public int getValue(){
rwl.readLock.lock();
int result = myValue;
rwl.readLock.unlock();
return result;
}
}
В Java операции над целыми числами являются атомарными, так что нет, в этом случае вам не нужно синхронизировать, если все, что вы делаете, это 1 запись и 1 чтение за раз.
Если это были длинные или двойные значения, вам необходимо выполнить синхронизацию, потому что возможно обновить часть long/double, затем прочитать другой поток и, наконец, обновить другую часть long/double.