В каких случаях требуется синхронный доступ к методам в Java? - PullRequest
19 голосов
/ 21 ноября 2008

В каких случаях необходимо синхронизировать доступ к членам экземпляра? Я понимаю, что доступ к статическим членам класса всегда должен быть синхронизирован, потому что они совместно используются всеми экземплярами объекта класса.

Мой вопрос: когда я буду неверен, если не синхронизирую членов экземпляра?

например, если мой класс

public class MyClass {
    private int instanceVar = 0;

    public setInstanceVar()
    {
        instanceVar++;
    }

    public getInstanceVar()
    {
        return instanceVar;
    }
}

в каких случаях (использования класса MyClass) мне понадобится , чтобы иметь методы: public synchronized setInstanceVar() и public synchronized getInstanceVar()?

Заранее спасибо за ваши ответы.

Ответы [ 6 ]

37 голосов
/ 21 ноября 2008

Модификатор 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).

19 голосов
/ 21 ноября 2008

Это зависит от того, хотите ли вы, чтобы ваш класс был потокобезопасным. Большинство классов не должны быть поточно-ориентированными (для простоты), в этом случае вам не нужна синхронизация. Если вам нужно, чтобы он был ориентирован на многопотоковое исполнение, вы должны синхронизировать доступ или , чтобы сделать переменную volatile. (Это позволяет избежать получения другими устаревшими данными устаревших данных.)

3 голосов
/ 21 ноября 2008

Если вы хотите сделать поток этого класса безопасным, я бы объявил instanceVar как volatile, чтобы вы всегда получали самое последнее значение из памяти, а также я бы сделал setInstanceVar() synchronized, потому что в JVM приращение не является атомарной операцией.

private volatile int instanceVar =0;

public synchronized setInstanceVar() { instanceVar++;

}
1 голос
/ 21 ноября 2008

Проще говоря, вы используете 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;
    }
}
1 голос
/ 21 ноября 2008

. Грубо говоря, ответ «это зависит». Синхронизация вашего установщика и получателя здесь будет иметь целью только гарантию того, что несколько потоков не смогут читать переменные между операциями приращения друг друга:

 synchronized increment()
 { 
       i++
 }

 synchronized get()
 {
   return i;
  }

но это на самом деле не сработает, потому что для того, чтобы поток вызывающей стороны получил то же значение, что и приращенный, вам нужно будет гарантировать, что вы увеличиваете атом, а затем извлекаете, чего вы здесь не делаете - то есть вы должны сделать что-то вроде

  synchronized int {
    increment
    return get()
  }

По сути, синхронизация полезна для определения того, какие операции должны быть гарантированно безопасными для выполнения потока (другими словами, вы не можете создать ситуацию, когда отдельный поток подрывает вашу операцию и заставляет ваш класс вести себя нелогично, или подрывает то, что вы ожидаете, состояние данных будет). На самом деле это большая тема, чем здесь.

Эта книга Параллелизм Java на практике превосходна и, безусловно, гораздо надежнее меня.

0 голосов
/ 21 ноября 2008

В Java операции над целыми числами являются атомарными, так что нет, в этом случае вам не нужно синхронизировать, если все, что вы делаете, это 1 запись и 1 чтение за раз.

Если это были длинные или двойные значения, вам нужно выполнить синхронизацию, потому что возможно обновить часть long / double, затем прочитать другой поток, а затем, наконец, обновить другую часть long / double.

...