Jakie przypadki wymagają synchronizowanego dostępu do metod w Javie?

W jakich przypadkach konieczna jest synchronizacja dostępu do członków instancji? Rozumiem, że dostęp do statycznych członków klasy zawsze musi być zsynchronizowany-ponieważ są one współdzielone przez wszystkie instancje obiektu klasy.

Moje pytanie brzmi, Kiedy będę niepoprawny, jeśli nie zsynchronizuję członków instancji?

Na przykład, jeśli moja klasa jest

public class MyClass {
    private int instanceVar = 0;

    public setInstanceVar()
    {
        instanceVar++;
    }

    public getInstanceVar()
    {
        return instanceVar;
    }
}

W jakich przypadkach (użycia klasy MyClass) chciałbym potrzebować aby mieć metody: public synchronized setInstanceVar() oraz public synchronized getInstanceVar() ?

Z góry dzięki za odpowiedzi.

Author: Daniel Spiewak, 2008-11-21

6 answers

To zależy od tego, czy chcesz, aby Twoja klasa była bezpieczna. Większość klas nie powinna być bezpieczna dla wątków (dla uproszczenia), w tym przypadku nie potrzebujesz synchronizacji. Jeśli chcesz, aby była bezpieczna dla wątków, powinieneś zsynchronizować access lub, aby zmienna była zmienna zmienna. (Unika innych wątków uzyskiwania "starych" danych.)

 19
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-21 18:01:27

Modyfikator synchronizedjest naprawdę złym pomysłem i należy go unikać za wszelką cenę. Myślę, że to godne pochwały, że Sun starał się, aby blokowanie trochę łatwiej osiągnąć, ale synchronized po prostu powoduje więcej kłopotów niż jest warte.

Problem polega na tym, że metoda synchronized jest w rzeczywistości cukrem składniowym służącym do uzyskania blokady this i przytrzymania jej przez czas trwania metody. Tak więc, public synchronized void setInstanceVar() byłoby równoważne coś takiego:

public void setInstanceVar() {
    synchronized(this) {
        instanceVar++;
    }
}

To jest złe dla dwóch uzasadnienie:

  • Wszystkie metody synchronized w tej samej klasie używają dokładnie tej samej blokady, co zmniejsza przepustowość
  • dostęp do zamka może uzyskać każdy, także członkowie innych klas.

Nic mnie nie powstrzyma przed zrobieniem czegoś takiego w innej klasie:

MyClass c = new MyClass();
synchronized(c) {
    ...
}

Wewnątrz tego bloku synchronized trzymam blokadę wymaganą przez wszystkie metody synchronized wewnątrz MyClass. To dodatkowo zmniejsza przepustowość i dramatycznie zwiększa szanse na impas.

Lepszym podejściem jest posiadanie dedykowanego obiektu lock i użycie bloku synchronized(...) bezpośrednio:

public class MyClass {
    private int instanceVar;
    private final Object lock = new Object();     // must be final!

    public void setInstanceVar() {
        synchronized(lock) {
            instanceVar++;
        }
    }
}

Alternatywnie można użyć interfejsu java.util.concurrent.Lock i implementacji java.util.concurrent.locks.ReentrantLock, aby osiągnąć zasadniczo ten sam wynik (w rzeczywistości jest to takie samo w Javie 6).

 35
Author: Daniel Spiewak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-21 18:18:50

Jeśli chcesz, aby ten wątek klasy był bezpieczny, zadeklarowałbym instanceVar jako volatile, aby upewnić się, że otrzymujesz zawsze najbardziej aktualną wartość z pamięci, a także zrobiłbym setInstanceVar() synchronized ponieważ w JVM przyrost nie jest operacją atomową.

private volatile int instanceVar =0;

public synchronized setInstanceVar() { instanceVar++;

}
 3
Author: bruno conde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-21 18:08:27

. Z grubsza odpowiedź brzmi: "to zależy". Synchronizacja settera i gettera tutaj miałaby tylko zamierzony cel zagwarantowania, że wiele wątków nie będzie w stanie odczytać zmiennych między sobą:

 synchronized increment()
 { 
       i++
 }

 synchronized get()
 {
   return i;
  }

Ale to naprawdę nie działa tutaj, ponieważ aby zapewnić, że Twój wątek rozmówcy ma tę samą wartość, którą zwiększa, musisz zagwarantować, że atomicznie zwiększasz, a następnie odzyskujesz, czego nie robisz tutaj-tj. coś jak

  synchronized int {
    increment
    return get()
  }

Zasadniczo synchronizacja jest przydatna do określenia, które operacje muszą być zagwarantowane, aby uruchomić threadsafe (inotherwords, nie możesz stworzyć sytuacji, w której oddzielny wątek podważa Twoją operację i sprawia, że twoja klasa zachowuje się nielogicznie lub podważa oczekiwany stan danych). To w rzeczywistości większy temat, niż można tutaj poruszyć.

Ta książka współbieżność Javy w praktyce jest doskonała, a na pewno znacznie więcej wiarygodny ode mnie.

 1
Author: Steve B.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-21 18:15:10

Mówiąc najprościej, używasz synchronized, gdy masz wątki mutliple uzyskujące dostęp do tej samej metody tej samej instancji, która zmieni stan obiektu / lub aplikacji.

Jest to prosty sposób, aby zapobiec Warunkom wyścigu między wątkami, a tak naprawdę powinieneś go używać tylko wtedy, gdy planujesz mieć współbieżne wątki uzyskujące dostęp do tej samej instancji, takiej jak obiekt globalny.

Teraz, gdy czytasz stan instancji obiektu z równoczesnym wątki, warto zajrzeć do Javy.util./ align = "left" / zamki.ReentrantReadWriteLock -- który teoretycznie pozwala na odczyt wielu wątków jednocześnie, ale tylko jeden wątek może pisać. Tak więc w przykładzie metody getter i setting, który wszyscy wydają się dawać, możesz wykonać następujące czynności:

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
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-21 18:46:26

W Javie operacje na ints są atomowe, więc nie, w tym przypadku nie musisz synchronizować, jeśli robisz tylko 1 zapis i 1 odczyt na raz.

Jeśli były to długie lub podwójne, musisz zsynchronizować, ponieważ jest możliwe, aby część long/double została zaktualizowana, a następnie odczytać inny wątek, a następnie w końcu druga część long / double została zaktualizowana.

 -3
Author: Outlaw Programmer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-21 18:04:32