Рубрики
Без рубрики

Ключевое слово Java synchronized, Синхронизированный метод и блок

Ключевое слово Java synchronized используется в многопоточности для создания блока кода, который может выполняться только одним потоком одновременно. Зачем нам это нужно

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