Автор оригинала: Pankaj Kumar.
Ключевое слово Java synchronized используется в многопоточности для создания блока кода, который может выполняться только одним потоком одновременно.
Зачем нам нужна синхронизация?
Когда у нас есть несколько потоков, работающих над общим объектом, конечный результат может быть поврежден. Допустим, у нас есть простая программа для увеличения переменной счетчика объекта. Эта переменная является общей для всех потоков.
package com.journaldev.threads;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class CounterThread implements Runnable {
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public void run() {
Random rand = new Random();
try {
Thread.sleep(rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
public static void main(String[] args) throws InterruptedException {
CounterThread ct = new CounterThread();
List threads = new ArrayList<>();
for (int i = 0; i < 100; i++) {
Thread t = new Thread(ct);
threads.add(t);
t.start();
}
// wait for every thread to finish
for (Thread t : threads) {
t.join();
}
System.out.println("Final Count = " + ct.getCount());
}
}
Мы используем метод соединения потоков (), чтобы убедиться, что каждый поток мертв, прежде чем мы напечатаем окончательный подсчет.
Мы также используем случайный класс, чтобы добавить некоторое время обработки в метод run ().
Если вы запустите вышеприведенную программу, вы заметите, что итоговое количество меняется почти каждый раз.
Причиной такого расхождения является оператор “count++”. Это не атомная операция.
Сначала считывается переменная count, затем к ней добавляется 1, а затем значение присваивается переменной count.
У нас есть несколько потоков, работающих над переменной count одновременно. Если поток считывает переменную count и, прежде чем он сможет ее обновить, другой поток обновит ее. Это приведет к повреждению данных в нашей программе.
Java предоставляет ключевое слово synchronized, чтобы помочь в этом сценарии, пометив код, который будет выполняться только одним потоком в любой момент времени.
Пример синхронизации Java
Давайте исправим вышеприведенную программу, используя ключевое слово synchronized. Мы можем создать синхронизированный блок вокруг операции “count++”.
Ключевое слово synchronized требует аргумента объекта, который будет использоваться для создания механизма блокировки. Для этой цели мы можем создать фиктивный объект в классе.
Ниже приведен обновленный код, который будет работать нормально, и итоговое количество будет равно 100. Я удалил общий код, чтобы сосредоточиться только на синхронизированном использовании ключевых слов.
public class CounterThread implements Runnable {
...
private final Object mutex = new Object();
...
public void run() {
...
synchronized (mutex) {
count++;
}
}
...
}
Пример синхронизированного ключевого слова Java
Как синхронизированное ключевое слово работает внутри компании?
Логика синхронизации Java построена вокруг внутренней сущности, называемой встроенной блокировкой или блокировкой монитора .
Когда поток пытается войти в синхронизированную область, он должен сначала получить блокировку объекта. Затем выполняются все операторы в синхронизированном блоке. Наконец, поток освобождает блокировку объекта, который может быть получен другими потоками в пуле ожидания.
Если объект “null”, ключевое слово synchronized вызовет исключение NullPointerException .
Блок синхронизации Java
Когда блок кода обернут вокруг ключевого слова synchronized, он называется синхронизированным блоком.
Синтаксис синхронизированного блока
synchronized (object) {
// syhcnronized block code
}
Вот простой пример синхронизированного блока в Java.
package com.journaldev.threads;
public class MyRunnable implements Runnable {
private int counter;
private final Object mutex = new Object();
@Override
public void run() {
doSomething();
synchronized (mutex) {
counter++;
}
}
private void doSomething() {
// some heavy lifting work
}
}
Метод синхронизации Java
Иногда требуется синхронизировать каждый оператор внутри метода. В этом случае мы можем синхронизировать сам метод.
Синтаксис синхронизированного метода
access_modifiers synchronized return_type method_name(method_parameters) {
method_code
}
Вот пример метода синхронизации java.
package com.journaldev.threads;
public class MyRunnable implements Runnable {
private int counter;
@Override
public void run() {
increment(2);
}
private synchronized void increment(int i) {
counter += i;
}
}
Блокировка объекта в Синхронизированном методе
Как и синхронизированный блок, синхронизированные методы также требуют блокировки объекта.
- Если метод статический , блокировка приобретается для класса.
- Если метод нестатичен, блокировка будет получена для текущего объекта.
Метод синхронизации Java против блока
- Метод синхронизации Java блокирует текущий объект, поэтому, если существует другой синхронизированный метод, другие потоки будут ожидать блокировки объекта, даже если в этих методах нет общей переменной. Синхронизированный блок Java работает с полем объекта, поэтому в этом случае лучше использовать синхронизированный блок.
- Если объект имеет несколько синхронизированных методов, работающих с одними и теми же переменными, то предпочтителен синхронизированный метод. Например, StringBuffer использует синхронизированные методы, поскольку все методы append() и insert() работают с одним и тем же объектом.
Вот пример, когда у нас есть несколько методов, работающих с одной и той же переменной, поэтому использование синхронизированного метода является лучшим выбором.
package com.journaldev.threads;
public class MyRunnable implements Runnable {
private int counter;
@Override
public void run() {
increment(2);
decrement(1);
}
private synchronized void increment(int i) {
counter += i;
}
private synchronized void decrement(int i) {
counter -= i;
}
}
Вот еще один пример, когда различные методы работают с другой общей переменной, поэтому использование синхронизированного блока является лучшим выбором.
package com.journaldev.threads;
public class MyRunnable implements Runnable {
private int positiveCounter;
private int negativeCounter;
private final Object positiveCounterMutex = new Object();
private final Object negativeCounterMutex = new Object();
@Override
public void run() {
increment(2);
decrement(1);
}
private void increment(int i) {
synchronized (positiveCounterMutex) {
positiveCounter += i;
}
}
private void decrement(int i) {
synchronized (negativeCounterMutex) {
negativeCounter -= i;
}
}
}
Вывод
Ключевое слово Java synchronized полезно для предотвращения повреждения данных при многопоточном программировании. Однако синхронизация снижает производительность кода из-за дополнительных накладных расходов на механизм блокировки. Использование синхронизированного блока или синхронизированного метода во многом зависит от требований вашего проекта.