Синхронизация относится к многопоточности. Синхронизированый блок кода может быть выполнен только одним потоком одновременно.
Java поддерживает несколько потоков для выполнения. Это может привести к тому, что два или более потока получат доступ к одному и тому же полю или объекту. Синхронизация это процесс, который позволяет выполнять все параллельные потоки в программе синхронно. Синхронизация позволяет избежать ошибок согласованности памяти, вызванные из-за непоследовательного доступа к общей памяти.
Когда метод объявлен как синхронизированный — нить держит монитор для объекта, метод которого исполняется. Если другой поток выполняет синхронизированный метод, ваш поток заблокируется до тех пор, пока другой поток не отпустит монитор.
Синхронизация достигается в Java использованием зарезервированного слова
synchronized
.
Вы можете использовать его в своих классах определяя синхронизированные методы или блоки. Вы не сможете использовать synchronized
в переменных или атрибутах в определении класса.
Блокировка на уровне объекта
Это механизм синхронизации не статического метода или не статического блока кода, такой, что только один поток сможет выполнить данный блок или метод на данном экземпляре класса.
Это нужно делать всегда, когда необходимо сделать данные на уровне экземпляра потокобезопасными.
Пример:
public class DemoClass{
public synchronized void demoMethod(){}
}
или
public class DemoClass{
public void demoMethod(){
synchronized (this) {
}
}
}
или
public class DemoClass{
private final Object lock = new Object();
public void demoMethod(){
synchronized (lock) {
}
}
}
Блокировка на уровне класса
Предотвращает возможность нескольким потокам войти в синхронизированный блок во время выполнения в любом из доступных экземпляров класса. Это означает, что если во время выполнения программы имеется 100 экземпляров класса
DemoClass
, то только один поток в это время сможет выполнить
demoMethod()
в любом из случаев, и все другие случаи будут заблокированы для других потоков.
Это необходимо когда требуется сделать статические данные потокобезопасными.
public class DemoClass{
public synchronized static void demoMethod(){}
}
или
public class DemoClass{
public void demoMethod(){
synchronized (DemoClass.class) {
}
}
}
или
public class DemoClass
{
private final static Object lock = new Object();
public void demoMethod(){
synchronized (lock) {
}
}
}
Некоторые важные замечания
- Синхронизация в Java гарантирует, что никакие два потока не смогут выполнить синхронизированный метод одновременно или параллельно.
synchronized
можно использовать только с методами и блоками кода. Эти методы или блоки могут быть статическими или не-статическими.
- когда какой либо поток входит в синхронизированный метод или блок он приобретает блокировку и всякий раз, когда поток выходит из синхронизированного метода или блока JVM снимает блокировку. Блокировка снимается, даже если нить оставляет синхронизированный метод после завершения из-за каких-либо ошибок или исключений.
synchronized
в Java рентерабельна это означает, что если синхронизированный метод вызывает другой синхронизированный метод, который требует такой же замок, то текущий поток, который держит замок может войти в этот метод не приобретая замок.
- Синхронизация в Java будет бросать
NullPointerException
если объект используемый в синхронизированном блоке null. Например, в вышеприведенном примере кода, если замок инициализируется как null
, синхронизированный (lock) бросит NullPointerException
.
- Синхронизированные методы в Java вносят дополнительные затраты на производительность вашего приложения. Так что используйте синхронизацию, когда она абсолютно необходима. Кроме того, рассмотрите вопрос об использовании синхронизированных блоков кода для синхронизации только критических секций кода.
- Вполне возможно, что и статический и не статический синхронизированные методы могут работать одновременно или параллельно, потому что они захватывают замок на другой объект.
- В соответствии со спецификацией языка вы не можете использовать
synchronized
в конструкторе это приведет к ошибке компиляции.
- Не синхронизируйте по не финальному (no final) полю, потому что ссылка, на не финальное поле может измениться в любое время, а затем другой поток может получить синхронизацию на разных объектах и уже не будет никакой синхронизации вообще. Лучше всего использовать класс
String
, который уже неизменяемый и финальный.
Удачи в обучении!!
Оригинал: Object level lock vs Class level lock in Java
При прочтении разнообразного материала может показаться, что синхронизация привносит затраты только в связи с блокировкой ресурса, как например пишут в одной статье с советами по многопоточному программированию на tproger.ru:
Однако затраты в связи с синхронизацией кроются не только здесь, сам вызов синхронизации блока несет за собой затраты процессорного времени: