Пример синхронизации потоков java

Синхронизация потоков в Java

Многопоточные программы могут регулярно сталкиваться с ситуацией, когда несколько потоков Java пытаются добраться до одного и того же ресурса. Это можно решить с помощью синхронизации в Java. Только один конкретный поток может получить доступ к ресурсу в заданное время.

Зачем использовать синхронизацию в Java?

Если вы начинаете, по крайней мере, с двумя потоками внутри программы, существует вероятность того, что несколько потоков попытаются добраться до одного и того же ресурса. Это может даже создать неожиданный результат из-за проблем параллелизма.

synchronized(objectidentifier) < // Access shared variables and other shared resources; >

Например, несколько потоков пытаются записать в эквивалентный файл. Это может повредить данные, так как один из потоков может переопределить данные, или когда поток одновременно открывает тот же файл, другой поток может закрыть тот же файл. Необходимо синхронизировать действие нескольких потоков. Это может быть реализовано с использованием концепции под названием Monitors.

  • Каждый объект в Java связан с монитором, который поток может заблокировать или разблокировать.
  • Только один поток за раз может удерживать блокировку на мониторе.
  • Язык программирования Java предоставляет очень удобный способ создания потоков и синхронизации их задач с помощью синхронизированных блоков.
  • Он также сохраняет общие ресурсы в этом конкретном блоке.

Синхронизированные блоки в Java помечаются ключевым словом Synchronized. Этот блок в Java синхронизируется на некотором объекте. Все блоки, которые синхронизируются на одном и том же объекте, могут иметь одновременно только один поток, выполняющийся внутри них.

Читайте также:  Html image src data base64

Все остальные потоки, пытающиеся войти в синхронизированный блок, блокируются до тех пор, пока поток внутри синхронизированного блока не выйдет из блока.

Типы синхронизации

Существует в основном два типа синхронизации.

  1. Синхронизация процессов: одновременное выполнение нескольких потоков или процессов для достижения такого состояния, при котором они совершают определенную последовательность действий.
  2. Синхронизация потоков: иногда, когда несколько потоков пытаются получить доступ к общему ресурсу, необходимо убедиться, что ресурс будет использоваться только одним потоком одновременно.

Реализация блокировки

Как я упоминал ранее, синхронизация строится вокруг внутренней сущности, известной как блокировка или монитор. У каждого объекта есть замок, связанный с ним. Таким образом, поток, которому необходим согласованный доступ к полям объекта, должен получить блокировку объекта перед тем, как получить к ним доступ, а затем снять блокировку, когда работа будет завершена.

Начиная с Java 5, пакет java.util.concurrent.locks содержит много реализаций блокировки.

Вот как выглядит блокировка:

public class Lock < private boolean isLocked = false; public synchronized void lock() throws InterruptedException < while(isLocked) < wait(); >isLocked = true; > public synchronized void unlock() < isLocked = false; notify(); >>

Метод lock() блокирует экземпляр Lock, так что все потоки, вызывающие lock(), блокируются до тех пор, пока не будет выполнена unlock().

Многопоточность без синхронизации

Вот простой пример, который печатает значение счетчика в последовательности, и каждый раз, когда мы его запускаем, он выдает другой результат в зависимости от доступности процессора для потока. Проверь это!

class Multithread < public void printCount() < try < for(int i = 5; i> catch (Exception e) < System.out.println("Thread interrupted."); >> > class Thread extends Multithread < private Thread t; private String threadName; Multithread MT; Thread( String name, Multithread mt) < threadName = name; MT= mt; >public void run() < MT.printCount(); System.out.println("Thread " + threadName + " exiting."); >public void start() < System.out.println("Starting " + threadName ); if (t == null) < t = new Thread (this, threadName); t.start(); >> > public class TestThread < public static void main(String args[]) < Multithread MT = new Multithread(); Thread t = new Thread( "Thread - 1 ", MT); Thread t1 = new Thread( "Thread - 2 ", MT); t.start(); t1.start(); // wait for threads to end try < t.join(); t1.join(); >catch ( Exception e) < System.out.println("Interrupted"); >> >

Вышеуказанные результаты программы:

Вывод-Синхронизация в Java

Многопоточность с синхронизацией

Это тот же пример, что и выше, но он печатает значение счетчика в последовательности. Каждый раз, когда мы запускаем его, он дает один и тот же результат.

class Multithread < public void printCount() < try < for(int i = 5; i >0; i--) < System.out.println("Counter --- " + i ); >> catch (Exception e) < System.out.println("Thread interrupted."); >> > class Thread extends Multithread < private Thread t; private String threadName; Multithread MT; Thread( String name, Multithread mt) < threadName = name; MT= mt; >public void run() < synchronized(MT) < MT.printCount(); >System.out.println("Thread " + threadName + " exiting."); > public void start() < System.out.println("Starting " + threadName ); if (t == null) < t = new Thread (this, threadName); t.start(); >> > public class TestThread < public static void main(String args[]) < Multithread MT = new Multithread(); Thread T = new Thread( "Thread - 1 ", MT); Thread T1 = new Thread( "Thread - 2 ", MT); T.start(); T1.start(); // wait for threads to end try < T.join(); T1.join(); >catch ( Exception e) < System.out.println("Interrupted"); >> >

результат

Synchronized ключевое слово Java помечает блок или метод как критический раздел. Критическая секция – это то, где одновременно выполняется только один поток, и этот поток удерживает блокировку синхронизированной секции. Это синхронизированное ключевое слово помогает в написании параллельных частей любого приложения. Он также защищает общие ресурсы внутри блока.

Синхронизированное ключевое слово может использоваться с:

Synchronized: блок кода

Общий синтаксис для записи синхронизированного блока:

Когда поток хочет выполнить синхронизированные операторы внутри блока, он должен получить блокировку на мониторе lockObject. Только один поток может получить монитор объекта блокировки одновременно.

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

  • Если поток переходит в режим сна (с использованием метода sleep() ), он не снимает блокировку. В течение этого времени ожидания ни один поток не будет выполнять операторы синхронизированного блока.
  • Синхронизация Java вызовет исключение NullPointerException, если объект блокировки, используемый в «synchronized (lock)», равен нулю.

synchronized : метод

Общий синтаксис написания синхронизированного метода:

 synchronized method ( parameters) < //synchronized code >

Здесь lockObject – это просто ссылка на объект, чья блокировка связана с монитором, который представляет синхронизированные операторы.

Подобно синхронизированному блоку, поток должен получить блокировку на подключенном объекте монитора с помощью синхронизированного метода. В случае синхронизированного метода объект блокировки:

  • Объект .class – если данный метод является статическим.
  • объект this – если метод нестатический. «this» – это ссылка на текущий объект, в котором вызывается синхронизированный метод.

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

Разница между синхронизированным ключевым словом и блоком

  • Когда вы используете синхронизированное ключевое слово с методом, оно получает блокировку объекта для всего метода. Это означает, что никакой другой поток не может использовать какой-либо синхронизированный метод до тех пор, пока текущий вызванный поток не завершит свое выполнение.
  • Синхронизированный блок получает блокировку в объекте только в скобках после того, как задано синхронизированное ключевое слово. Это означает, что никакой другой поток не может получить блокировку на уже заблокированном объекте, пока блок не выйдет. Но другие потоки смогут получить доступ к остальной части кода, который присутствует в методе.

Источник

Пример синхронизации потоков java

При работе потоки нередко обращаются к каким-то общим ресурсам, которые определены вне потока, например, обращение к какому-то файлу. Если одновременно несколько потоков обратятся к общему ресурсу, то результаты выполнения программы могут быть неожиданными и даже непредсказуемыми. Например, определим следующий код:

public class Program < public static void main(String[] args) < CommonResource commonResource= new CommonResource(); for (int i = 1; i < 6; i++)< Thread t = new Thread(new CountThread(commonResource)); t.setName("Thread "+ i); t.start(); >> > class CommonResource < int x=0; >class CountThread implements Runnable < CommonResource res; CountThread(CommonResource res)< this.res=res; >public void run() < res.x=1; for (int i = 1; i < 5; i++)< System.out.printf("%s %d \n", Thread.currentThread().getName(), res.x); res.x++; try< Thread.sleep(100); >catch(InterruptedException e)<> > > >

Здесь определен класс CommonResource , который представляет общий ресурс и в котором определено одно целочисленное поле x.

Этот ресурс используется классом потока CountThread. Этот класс просто увеличивает в цикле значение x на единицу. Причем при входе в поток значение x=1:

То есть в итоге мы ожидаем, что после выполнения цикла res.x будет равно 4.

В главном классе программы запускается пять потоков. То есть мы ожидаем, что каждый поток будет увеличивать res.x с 1 до 4 и так пять раз. Но если мы посмотрим на результат работы программы, то он будет иным:

Thread 1 1 Thread 2 1 Thread 3 1 Thread 5 1 Thread 4 1 Thread 5 6 Thread 2 6 Thread 1 6 Thread 3 6 Thread 4 6 Thread 4 11 Thread 2 11 Thread 5 11 Thread 3 11 Thread 1 11 Thread 4 16 Thread 1 16 Thread 3 16 Thread 5 16 Thread 2 16

То есть пока один поток не окончил работу с полем res.x, с ним начинает работать другой поток.

Чтобы избежать подобной ситуации, надо синхронизировать потоки. Одним из способов синхронизации является использование ключевого слова synchronized . Этот оператор предваряет блок кода или метод, который подлежит синхронизации. Для его применения изменим класс CountThread:

class CountThread implements Runnable < CommonResource res; CountThread(CommonResource res)< this.res=res; >public void run() < synchronized(res)< res.x=1; for (int i = 1; i < 5; i++)< System.out.printf("%s %d \n", Thread.currentThread().getName(), res.x); res.x++; try< Thread.sleep(100); >catch(InterruptedException e)<> > > > >

При создании синхронизированного блока кода после оператора synchronized идет объект-заглушка: synchronized(res) . Причем в качестве объекта может использоваться только объект какого-нибудь класса, но не примитивного типа.

Каждый объект в Java имеет ассоциированный с ним монитор . Монитор представляет своего рода инструмент для управления доступа к объекту. Когда выполнение кода доходит до оператора synchronized, монитор объекта res блокируется, и на время его блокировки монопольный доступ к блоку кода имеет только один поток, который и произвел блокировку. После окончания работы блока кода, монитор объекта res освобождается и становится доступным для других потоков.

После освобождения монитора его захватывает другой поток, а все остальные потоки продолжают ожидать его освобождения.

В итоге консольный вывод изменится:

Thread 1 1 Thread 1 2 Thread 1 3 Thread 1 4 Thread 3 1 Thread 3 2 Thread 3 3 Thread 3 4 Thread 5 1 Thread 5 2 Thread 5 3 Thread 5 4 Thread 4 1 Thread 4 2 Thread 4 3 Thread 4 4 Thread 2 1 Thread 2 2 Thread 2 3 Thread 2 4

При применении оператора synchronized к методу пока этот метод не завершит выполнение, монопольный доступ имеет только один поток — первый, который начал его выполнение. Для применения synchronized к методу, изменим классы программы:

public class Program < public static void main(String[] args) < CommonResource commonResource= new CommonResource(); for (int i = 1; i < 6; i++)< Thread t = new Thread(new CountThread(commonResource)); t.setName("Thread "+ i); t.start(); >> > class CommonResource < int x; synchronized void increment()< x=1; for (int i = 1; i < 5; i++)< System.out.printf("%s %d \n", Thread.currentThread().getName(), x); x++; try< Thread.sleep(100); >catch(InterruptedException e)<> > > > class CountThread implements Runnable < CommonResource res; CountThread(CommonResource res)< this.res=res; >public void run() < res.increment(); >>

Результат работы в данном случае будет аналогичен примеру выше с блоком synchronized. Здесь опять в дело вступает монитор объекта CommonResource — общего объекта для всех потоков. Поэтому синхронизированным объявляется не метод run() в классе CountThread, а метод increment класса CommonResource. Когда первый поток начинает выполнение метода increment, он захватывает монитор объекта CommonResource. А все потоки также продолжают ожидать его освобождения.

Источник

Оцените статью