Какие случаи требуют доступа к синхронизированным методам в Java?

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

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

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

public class MyClass {
    private int instanceVar = 0;

    public setInstanceVar()
    {
        instanceVar++;
    }

    public getInstanceVar()
    {
        return instanceVar;
    }
}

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

Заранее благодарим за ваши ответы.

Ответы

Ответ 1

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

Ответ 2

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

Ответ 3

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

private volatile int instanceVar =0;

public synchronized setInstanceVar() { instanceVar++;

}

Ответ 4

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

 synchronized increment()
 { 
       i++
 }

 synchronized get()
 {
   return i;
  }

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

  synchronized int {
    increment
    return get()
  }

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

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

Ответ 5

Чтобы просто сказать, вы используете синхронизацию, когда у вас есть mutliple threads, доступ к одному и тому же методу того же экземпляра, который изменит состояние объекта/приложения.

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

Теперь, когда вы читаете состояние экземпляра объекта с параллельными потоками, вы можете посмотреть в java.util.concurrent.locks.ReentrantReadWriteLock - который теоретически позволяет много потоков читать за раз, но разрешено писать только один поток. Таким образом, в примере метода getter и метода настройки, который все, кажется, дают, вы можете сделать следующее:

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;
    }
}

Ответ 6

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

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