Я погоджуюсь з одним із зауважень Джона: Ви завжди повинні використовувати фіктивну фіктивну фіксацію під час доступу до не остаточної змінної, щоб запобігти невідповідностям у разі зміни посилання на змінну. Отже, у будь-яких випадках і як перше правило:
Правило №1: Якщо поле не є остаточним, завжди використовуйте (приватний) фінальний фіктивний замок.
Причина №1: Ви тримаєте замок і змінюєте посилання на змінну самостійно. Інший потік, який чекає за межами синхронізованого блокування, зможе увійти в захищений блок.
Причина 2: Ви тримаєте замок, а інший потік змінює посилання на змінну. Результат той самий: інший потік може потрапити в захищений блок.
Але при використанні фінального фіктивного фіксатора виникає ще одна проблема : ви можете отримати неправильні дані, оскільки ваш нефінальний об'єкт буде синхронізований лише з оперативною пам'яттю під час виклику синхронізації (об'єкта). Отже, як друге правило:
Правило №2: При блокуванні нефінального об'єкта вам завжди потрібно робити обидва: Використання фіктивного фіктивного блокування та блокування нефінального об'єкта задля синхронізації оперативної пам'яті. (Єдиною альтернативою буде оголошення всіх полів об’єкта мінливими!)
Ці замки ще називають «вкладеними замками». Зверніть увагу, що ви повинні телефонувати їм завжди в однаковому порядку, інакше ви отримаєте глухий замок :
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
}
}
}
}
Як бачите, я записую два замки безпосередньо в одному рядку, тому що вони завжди належать разом. Ось так ви могли б навіть зробити 10 вкладених замків:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
}
}
}
}
Зверніть увагу, що цей код не зламається, якщо ви просто придбаєте внутрішній замок, як synchronized (LOCK3)
у інших потоках. Але це зламається, якщо ви зателефонуєте в інший потік приблизно так:
synchronized (LOCK4) {
synchronized (LOCK1) {
synchronized (LOCK3) {
synchronized (LOCK2) {
}
}
}
}
Існує лише одне обхідне рішення щодо таких вкладених блокувань під час обробки нефінальних полів:
Правило №2 - Альтернатива: оголосіть усі поля об’єкта мінливими. (Я не буду тут говорити про недоліки цього, наприклад, запобігання будь-якому сховищу в кешах рівня x навіть для читання, aso.)
Тож aioobe цілком має рацію: просто використовуйте java.util.concurrent. Або починайте розуміти все, що стосується синхронізації, і робіть це самостійно за допомогою вкладених замків. ;)
Щоб отримати докладнішу інформацію, чому синхронізація в нефінальних полях переривається, ознайомтесь із моїм тестовим прикладом: https://stackoverflow.com/a/21460055/2012947
А для більш детальної інформації, чому вам взагалі потрібна синхронізація завдяки оперативній пам’яті та кешам, дивіться тут: https://stackoverflow.com/a/21409975/2012947
o
посилався на момент досягнення синхронізованого блоку. Якщо об'єкт, щоo
посилається на зміни, може змінитися інший потік і виконати синхронізований блок коду.