digital-garden/dev/java/synchronized.md

143 lines
8.6 KiB
Markdown
Raw Normal View History

2024-10-09 09:23:45 +03:00
---
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) -->