143 lines
8.6 KiB
Markdown
143 lines
8.6 KiB
Markdown
|
---
|
|||
|
aliases:
|
|||
|
tags:
|
|||
|
- maturity/🌱
|
|||
|
date: 2024-10-09
|
|||
|
zero-link:
|
|||
|
parents:
|
|||
|
linked:
|
|||
|
---
|
|||
|
synchronized — это один из ключевых механизмов в Java для управления [[Многопоточность в Java|многопоточностью]] и обеспечения безопасности при доступе к разделяемым ресурсам. Этот модификатор используется для того, чтобы ограничить доступ к критическим секциям кода, тем самым предотвращая состояния гонки ([[../other/Race condition|race conditions]]).
|
|||
|
|
|||
|
**Как работает synchronized**
|
|||
|
Synchronized может применяться к методам или блокам кода. Когда поток входит в метод или блок, помеченный `synchronized`, он получает [[Монитор в Java|монитор]] (lock) на объект, связанный с этим кодом. Другие потоки не могут получить доступ к этому коду до тех пор, пока монитор не будет освобожден. Это гарантирует, что только один поток может одновременно выполнять данный код.
|
|||
|
|
|||
|
В этом примере, `synchronized` гарантирует, что методы `increment` и `getCount` не будут выполняться одновременно несколькими потоками, предотвращая некорректные обновления переменной `count`.
|
|||
|
|
|||
|
```java
|
|||
|
public class Counter {
|
|||
|
private int count = 0;
|
|||
|
|
|||
|
public synchronized void increment() {
|
|||
|
count++;
|
|||
|
}
|
|||
|
|
|||
|
public synchronized int getCount() {
|
|||
|
return count;
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Иногда удобнее синхронизировать не весь метод, а только его часть. В этом случае можно использовать блок `synchronized`:
|
|||
|
|
|||
|
```java
|
|||
|
public void increment() {
|
|||
|
synchronized (this) {
|
|||
|
count++;
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
**Особенности**
|
|||
|
- **Мониторы привязаны к объектам**, а не к методам. Если вы синхронизируете метод экземпляра (instance method), то монитором является текущий объект (this). Если метод статический, то монитором выступает сам класс (ClassName.class).
|
|||
|
- **Блокировки могут привести к взаимной блокировке** ([[../../../../_inbox/Deadlock|deadlock]]), если два потока пытаются захватить друг у друга мониторы в неправильном порядке.
|
|||
|
|
|||
|
**Ограничения**
|
|||
|
- **Синхронизация снижает производительность**, так как потоки вынуждены ожидать освобождения блокировки.
|
|||
|
- **Недостаточная гибкость:** при сложных задачах многопоточности использование `synchronized` может стать неэффективным, и тогда лучше рассмотреть более современные и гибкие альтернативы, такие как классы из пакета `java.util.concurrent` (например, `ReentrantLock`).
|
|||
|
|
|||
|
## Частые проблемы и ошибки
|
|||
|
### Чрезмерная синхронизация (Over-synchronization)
|
|||
|
**Проблема:** Синхронизация всего метода или слишком большого объема кода может снизить производительность программы, так как потоки вынуждены ждать доступа к синхронизированным участкам, даже если это не требуется.
|
|||
|
|
|||
|
**Решение:** Следует синхронизировать только ту часть кода, которая действительно требует защиты.
|
|||
|
|
|||
|
### Синхронизация на неправильном объекте
|
|||
|
**Проблема:** Если синхронизация происходит на локальном или временном объекте, это не защитит данные, так как каждый поток будет работать с собственной копией объекта.
|
|||
|
|
|||
|
```java
|
|||
|
public class Counter {
|
|||
|
private int count = 0;
|
|||
|
|
|||
|
public void increment() {
|
|||
|
// Неправильная синхронизация на локальном объекте
|
|||
|
Object lock = new Object();
|
|||
|
synchronized (lock) {
|
|||
|
count++;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Здесь каждый поток создает собственный объект `lock`, и, следовательно, синхронизация не работает.
|
|||
|
|
|||
|
**Решение:** Синхронизацию нужно выполнять на общем объекте (например, `this` или другом общем объекте).
|
|||
|
|
|||
|
### Синхронизация на статических и нестатических методах
|
|||
|
**Проблема:** Если не понимать, как работают мониторы для статических и нестатических методов, можно случайно создать ситуацию, когда доступ к объектам будет синхронизирован некорректно.
|
|||
|
|
|||
|
```java
|
|||
|
public class StaticSyncExample {
|
|||
|
private static int staticCount = 0;
|
|||
|
private int instanceCount = 0;
|
|||
|
|
|||
|
public static synchronized void incrementStatic() {
|
|||
|
staticCount++;
|
|||
|
}
|
|||
|
|
|||
|
public synchronized void incrementInstance() {
|
|||
|
instanceCount++;
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Здесь вызов `incrementStatic` синхронизируется на классе `StaticSyncExample.class`, а `incrementInstance` — на конкретном объекте класса. Это может привести к недоразумениям в случаях, когда ожидается один и тот же монитор для всего кода.
|
|||
|
|
|||
|
Еще один пример
|
|||
|
```java
|
|||
|
public class Container {
|
|||
|
private static final List<String> list = new ArrayList<>();
|
|||
|
|
|||
|
synchronized void addEntry(String s) {
|
|||
|
list.add(s)
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Здесь у нас будет проблема, если будет 2+ объекта класса `Container`, так как каждый из них будет синхронизироваться на своем объекте, а переменная `list` у нас статическая и ее экземпляр единственный на всю программу
|
|||
|
|
|||
|
**Решение:** Нужно учитывать, что статические и нестатические методы используют разные мониторы. Чтобы синхронизировать весь класс (включая как статические, так и нестатические данные), можно использовать общий объект для синхронизации.
|
|||
|
### Пропуск синхронизации для чтения и записи
|
|||
|
**Проблема:** Разрешение чтения переменных без синхронизации, тогда как их запись синхронизирована, может привести к непредсказуемым результатам, поскольку значения могут быть прочитаны в неконсистентном состоянии.
|
|||
|
|
|||
|
```java
|
|||
|
public class Counter {
|
|||
|
private int count = 0;
|
|||
|
|
|||
|
public synchronized void increment() {
|
|||
|
count++;
|
|||
|
}
|
|||
|
|
|||
|
// Неправильное чтение без синхронизации
|
|||
|
public int getCount() {
|
|||
|
return count;
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
В этом случае разные потоки могут получить некорректное значение переменной count.
|
|||
|
|
|||
|
***
|
|||
|
## Мета информация
|
|||
|
**Область**:: [[../../meta/zero/00 Java разработка|00 Java разработка]]
|
|||
|
**Родитель**:: [[Многопоточность в Java]]
|
|||
|
**Источник**::
|
|||
|
**Создана**:: [[2024-10-09]]
|
|||
|
**Автор**::
|
|||
|
### Дополнительные материалы
|
|||
|
-
|
|||
|
|
|||
|
### Дочерние заметки
|
|||
|
<!-- QueryToSerialize: LIST FROM [[]] WHERE contains(Родитель, this.file.link) or contains(parents, this.file.link) -->
|
|||
|
|