Автор оригинала: 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(); Listthreads = 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 полезно для предотвращения повреждения данных при многопоточном программировании. Однако синхронизация снижает производительность кода из-за дополнительных накладных расходов на механизм блокировки. Использование синхронизированного блока или синхронизированного метода во многом зависит от требований вашего проекта.