что такое синхронизация в java
Синхронизация в Java. Часть 1
Jul 9, 2020 · 6 min read
Прежде чем перейти к самой синхронизации, я объясню многопоточность на примере простого кода.
Первым классом будет класс “Countdown”, а класс “ThreadColor” будет выглядеть вот так:
Здесь я создал второй класс, расширяющий Thread:
Переходим к методу main, так как мы создали два класса, а затем запустили два потока из инструкции switch (рисунок 1), которая состоит из первого потока “Thread 1”, выводящего голубой текст, и второго потока “Thread 2”, выводящего фиолетовый текст.
Теперь давайте посмотрим, что происходит.
Здесь вы видите Thread1 в голубом цвете, а Thread2 в фиолетовом. Мы не можем предсказать, каков будет результат, т.е. порядок этих двух цветов. Можете заметить, что, повторяя выполнение кода, мы будем получать разный вывод.
А те п ерь мы добавим переменную экземпляра “ Private int I;”, которая заменит локальную переменную “ I”. Взглянем на результат:
Теперь он получился совсем иным. Вместо последовательного выполнения каждым потоком отсчёта от 10 до 1, мы видим, что некоторые числа повторяются.
Почему?
Очевидно, что повторяется число 10, а также несколько других. Единственное же, что мы сделали, — это поменяли локальную переменную на переменную экземпляра:
Куча — это память приложения, которая совместно используется всеми потоками, будучи разделённой на их стеки, которые представляют собой отдельные отсеки памяти для каждого потока.
Говоря проще, никакой поток не может обратиться к стеку другого потока, но при этом все потоки имеют доступ к куче. Локальная переменная хранится в стеке потока, поэтому каждый поток содержит свою собственную копию этой переменной, в то время как переменные экземпляра хранятся в куче.
Поэтому, когда несколько потоков работают совместно над одним объектом, они используют этот объект вместе. В этом случае если один поток изменяет значение, другой поток использует это изменённое значение. Аналогичным образом, когда “i” выступала в роли локальной переменной, потоки имели свои собственные версии этой переменной, но как только мы сделали “i” переменной экземпляра, два потока стали обращаться к этому общему ресурсу, хранящемуся в куче, поэтому каждый поток и пропускал некоторые числа.
Цикл for
Он уменьшает I на 1 и проверяет условие i>0. Суть цикла for заключается в выполнении нескольких шагов, а именно уменьшения, проверки и т.д. Отсюда получается, что поток может быть приостановлен между этими шагами. Он может быть приостановлен после уменьшения “i”, перед проверкой состояния или же сразу после выполнения всего кода и вывода результата в консоль. Уменьшение “i”, проверка условия и вывод в консоль значений: эти три шага могут послужить причиной остановки текущего потока.
Как можно догадаться, в первой попытке оба потока рассматривали значение “i” как 10, поэтому Thread 1 вывел 9, но Thread 2 вывел 8. Почему?
В то время как Thread 1 выполнял цикл for, Thread 2 должно быть его опередил, получил значение “i” в виде 9, выполнил блок for и вывел 8.
При обращении к общим ресурсам, мы вынуждены пройти через эту ситуацию, которая называется “ Thread interference” (коллизия потоков) или “ Race condition” (состояние гонки). Помните, что всегда возникает серьёзная проблема, когда дело доходит до написания или обновления общего ресурса.
Мы можем сделать это без пропуска чисел или избежания коллизии, т.е. передать один и тот же объект Countdown обоим потокам.
Взгляните на экземпляр выше, где присутствуют два новых объекта для потоков, не использующих общую кучу. Проверьте результат и вы не увидите никакой коллизии. Каждый поток успешно выполняет отсчёт от 10 до 1.
Но главный вопрос в том, будет ли это применимо в реальных ситуациях? Будет ли это работать, к примеру, для счёта в банке, где кто-либо вносит на него деньги, в то время как вы снимаете некую сумму с банкомата? Отсюда следует, что нам нужно использовать одинаковый объект с целью поддержания целостности данных, поскольку это единственный способ, который позволяет нам знать точный баланс счёта в банке после выполнения нескольких потоков (транзакций). Ведь так?
В схожих ситуациях может одновременно присутствовать несколько потоков, ожидающих своей очереди на изменение баланса счёта. Следовательно, нам нужно позволить этим нескольким потокам изменить его, предотвратив при этом состояние гонки.
Синхронизация
Поскольку реальные приложения не могут использовать приведённую выше реализацию, нам нужно искать решение, которое не избегает состояния гонки в процессе изменения общего ресурса. Для этого мы можем добавить в объявление метода ключевое слово synchronized, что позволит синхронизировать этот метод:
После добавления ключевого слова synchronized весь метод выполняется до того, как к нему получит доступ другой поток.
Следовательно, в этом сценарии два потока никогда не столкнутся.
Но является ли этот способ единственным для предотвращения состояния гонки?
В принципе, мы можем добавить synchronized только в блок инструкции, а не для всего метода.
Каждый объект в Java имеет Intrinsic Lock (монитор). Когда синхронизированный метод вызывается из потока, ему нужно получить этот монитор. Монитор будет освобождён после того, как поток завершит выполнение метода. Таким образом, мы можем синхронизировать блок инструкций, работающий с объектом, принудив потоки получать монитор, прежде чем выполнять блок инструкций. Помните, что монитор одновременно может удерживаться только одним потоком, поэтому другие потоки, желающие получить его, будут приостановлены до завершения работы текущего потока. Только после этого конкретный ожидающий поток сможет получить монитор и продолжить выполнение.
Единственный блок кода метода “doCountdown”, в который мы можем добавить ключевое слово synchronized, — это блок “цикла for”. Итак, какой же объект нам следует использовать для синхронизации цикла for? Переменную “i”? Не думаю, потому что это примитивный тип, а не объект. Монитор же присутствует только в объектах. А что насчёт объекта “color”? Давайте просто удалим synchronized из объявления метода и добавим следующим образом:
Вы видите тот же результат, что и на рисунке 5 в коде, где синхронизация не применялась. Почему же? Мы используем локальную переменную “color” для синхронизации. Как я уже пояснял выше относительно стеков потоков и прочего, использование локальной переменной здесь не работает, но объекты String переиспользуются внутри jvm, так как jvm для размещения строчных объектов использует пулы строк. Да, иногда это тоже может оказаться подходящим решением.
В качестве правила просто помните, что не нужно использовать локальную переменную для синхронизации.
Итак, давайте обновим синхронизированный блок кода таким образом:
Взглянув на результат, вы увидите, что потоки не столкнутся и не пропустят числа. Блок цикла for одновременно может выполняться только одним потоком.
Кроме того, мы можем синхронизировать статические методы и использовать статические объекты.
Синхронизация в Java. Часть 1
Прежде чем перейти к самой синхронизации, я объясню многопоточность на примере простого кода.
Первым классом будет класс “Countdown”, а класс “ThreadColor” будет выглядеть вот так:
Здесь я создал второй класс, расширяющий Thread:
Переходим к методу main, так как мы создали два класса, а затем запустили два потока из инструкции switch (рисунок 1), которая состоит из первого потока “Thread 1”, выводящего голубой текст, и второго потока “Thread 2”, выводящего фиолетовый текст.
Теперь давайте посмотрим, что происходит.
Здесь вы видите Thread1 в голубом цвете, а Thread2 в фиолетовом. Мы не можем предсказать, каков будет результат, т.е. порядок этих двух цветов. Можете заметить, что, повторяя выполнение кода, мы будем получать разный вывод.
А теперь мы добавим переменную экземпляра “Private int I;”, которая заменит локальную переменную “I”. Взглянем на результат:
Теперь он получился совсем иным. Вместо последовательного выполнения каждым потоком отсчёта от 10 до 1, мы видим, что некоторые числа повторяются.
Почему?Очевидно, что повторяется число 10, а также несколько других. Единственное же, что мы сделали, — это поменяли локальную переменную на переменную экземпляра:
Куча — это память приложения, которая совместно используется всеми потоками, будучи разделённой на их стеки, которые представляют собой отдельные отсеки памяти для каждого потока.
Говоря проще, никакой поток не может обратиться к стеку другого потока, но при этом все потоки имеют доступ к куче. Локальная переменная хранится в стеке потока, поэтому каждый поток содержит свою собственную копию этой переменной, в то время как переменные экземпляра хранятся в куче.
Поэтому, когда несколько потоков работают совместно над одним объектом, они используют этот объект вместе. В этом случае если один поток изменяет значение, другой поток использует это изменённое значение. Аналогичным образом, когда “i” выступала в роли локальной переменной, потоки имели свои собственные версии этой переменной, но как только мы сделали “i” переменной экземпляра, два потока стали обращаться к этому общему ресурсу, хранящемуся в куче, поэтому каждый поток и пропускал некоторые числа.
Цикл for
Он уменьшает I на 1 и проверяет условие i>0. Суть цикла for заключается в выполнении нескольких шагов, а именно уменьшения, проверки и т.д. Отсюда получается, что поток может быть приостановлен между этими шагами. Он может быть приостановлен после уменьшения “i”, перед проверкой состояния или же сразу после выполнения всего кода и вывода результата в консоль. Уменьшение “i”, проверка условия и вывод в консоль значений: эти три шага могут послужить причиной остановки текущего потока.
Как можно догадаться, в первой попытке оба потока рассматривали значение “i” как 10, поэтому Thread 1 вывел 9, но Thread 2 вывел 8. Почему?
В то время как Thread 1 выполнял цикл for, Thread 2 должно быть его опередил, получил значение “i” в виде 9, выполнил блок for и вывел 8.
При обращении к общим ресурсам, мы вынуждены пройти через эту ситуацию, которая называется “Thread interference” (коллизия потоков) или “Race condition” (состояние гонки). Помните, что всегда возникает серьёзная проблема, когда дело доходит до написания или обновления общего ресурса.
Мы можем сделать это без пропуска чисел или избежания коллизии, т.е. передать один и тот же объект Countdown обоим потокам.
Взгляните на экземпляр выше, где присутствуют два новых объекта для потоков, не использующих общую кучу. Проверьте результат и вы не увидите никакой коллизии. Каждый поток успешно выполняет отсчёт от 10 до 1.
Но главный вопрос в том, будет ли это применимо в реальных ситуациях? Будет ли это работать, к примеру, для счёта в банке, где кто-либо вносит на него деньги, в то время как вы снимаете некую сумму с банкомата? Отсюда следует, что нам нужно использовать одинаковый объект с целью поддержания целостности данных, поскольку это единственный способ, который позволяет нам знать точный баланс счёта в банке после выполнения нескольких потоков (транзакций). Ведь так?
В схожих ситуациях может одновременно присутствовать несколько потоков, ожидающих своей очереди на изменение баланса счёта. Следовательно, нам нужно позволить этим нескольким потокам изменить его, предотвратив при этом состояние гонки.
Синхронизация
Поскольку реальные приложения не могут использовать приведённую выше реализацию, нам нужно искать решение, которое не избегает состояния гонки в процессе изменения общего ресурса. Для этого мы можем добавить в объявление метода ключевое слово synchronized, что позволит синхронизировать этот метод:
После добавления ключевого слова synchronized весь метод выполняется до того, как к нему получит доступ другой поток.
Следовательно, в этом сценарии два потока никогда не столкнутся.
Но является ли этот способ единственным для предотвращения состояния гонки?
В принципе, мы можем добавить synchronized только в блок инструкции, а не для всего метода.
Каждый объект в Java имеет Intrinsic Lock(монитор). Когда синхронизированный метод вызывается из потока, ему нужно получить этот монитор. Монитор будет освобождён после того, как поток завершит выполнение метода. Таким образом, мы можем синхронизировать блок инструкций, работающий с объектом, принудив потоки получать монитор, прежде чем выполнять блок инструкций. Помните, что монитор одновременно может удерживаться только одним потоком, поэтому другие потоки, желающие получить его, будут приостановлены до завершения работы текущего потока. Только после этого конкретный ожидающий поток сможет получить монитор и продолжить выполнение.
Единственный блок кода метода “doCountdown”, в который мы можем добавить ключевое слово synchronized, — это блок “цикла for”. Итак, какой же объект нам следует использовать для синхронизации цикла for? Переменную “i”? Не думаю, потому что это примитивный тип, а не объект. Монитор же присутствует только в объектах. А что насчёт объекта “color”? Давайте просто удалим synchronized из объявления метода и добавим следующим образом:
Вы видите тот же результат, что и на рисунке 5 в коде, где синхронизация не применялась. Почему же? Мы используем локальную переменную “color” для синхронизации. Как я уже пояснял выше относительно стеков потоков и прочего, использование локальной переменной здесь не работает, но объекты String переиспользуются внутри jvm, так как jvm для размещения строчных объектов использует пулы строк. Да, иногда это тоже может оказаться подходящим решением.
В качестве правила просто помните, что не нужно использовать локальную переменную для синхронизации.
Итак, давайте обновим синхронизированный блок кода таким образом:
Взглянув на результат, вы увидите, что потоки не столкнутся и не пропустят числа. Блок цикла for одновременно может выполняться только одним потоком.
Кроме того, мы можем синхронизировать статические методы и использовать статические объекты.
Синхронизация потоков в Java
Многопоточные программы могут регулярно сталкиваться с ситуацией, когда несколько потоков Java пытаются добраться до одного и того же ресурса. Это можно решить с помощью синхронизации в Java. Только один конкретный поток может получить доступ к ресурсу в заданное время.
Зачем использовать синхронизацию в Java?
Если вы начинаете, по крайней мере, с двумя потоками внутри программы, существует вероятность того, что несколько потоков попытаются добраться до одного и того же ресурса. Это может даже создать неожиданный результат из-за проблем параллелизма.
Например, несколько потоков пытаются записать в эквивалентный файл. Это может повредить данные, так как один из потоков может переопределить данные, или когда поток одновременно открывает тот же файл, другой поток может закрыть тот же файл. Необходимо синхронизировать действие нескольких потоков. Это может быть реализовано с использованием концепции под названием Monitors.
Синхронизированные блоки в Java помечаются ключевым словом Synchronized. Этот блок в Java синхронизируется на некотором объекте. Все блоки, которые синхронизируются на одном и том же объекте, могут иметь одновременно только один поток, выполняющийся внутри них.
Все остальные потоки, пытающиеся войти в синхронизированный блок, блокируются до тех пор, пока поток внутри синхронизированного блока не выйдет из блока.
Типы синхронизации
Существует в основном два типа синхронизации.
Реализация блокировки
Как я упоминал ранее, синхронизация строится вокруг внутренней сущности, известной как блокировка или монитор. У каждого объекта есть замок, связанный с ним. Таким образом, поток, которому необходим согласованный доступ к полям объекта, должен получить блокировку объекта перед тем, как получить к ним доступ, а затем снять блокировку, когда работа будет завершена.
Начиная с Java 5, пакет java.util.concurrent.locks содержит много реализаций блокировки.
Вот как выглядит блокировка:
Метод lock() блокирует экземпляр Lock, так что все потоки, вызывающие lock(), блокируются до тех пор, пока не будет выполнена unlock().
Многопоточность без синхронизации
Вот простой пример, который печатает значение счетчика в последовательности, и каждый раз, когда мы его запускаем, он выдает другой результат в зависимости от доступности процессора для потока. Проверь это!
Вышеуказанные результаты программы:
Многопоточность с синхронизацией
Это тот же пример, что и выше, но он печатает значение счетчика в последовательности. Каждый раз, когда мы запускаем его, он дает один и тот же результат.
Результат изображен ниже:
Synchronized ключевое слово Java помечает блок или метод как критический раздел. Критическая секция – это то, где одновременно выполняется только один поток, и этот поток удерживает блокировку синхронизированной секции. Это синхронизированное ключевое слово помогает в написании параллельных частей любого приложения. Он также защищает общие ресурсы внутри блока.
Синхронизированное ключевое слово может использоваться с:
Synchronized: блок кода
Общий синтаксис для записи синхронизированного блока:
Когда поток хочет выполнить синхронизированные операторы внутри блока, он должен получить блокировку на мониторе lockObject. Только один поток может получить монитор объекта блокировки одновременно.
Таким образом, все другие потоки должны ждать, пока текущий исполняющий поток не получит блокировку и завершить свое выполнение. Ключевое слово synchronized гарантирует, что только один поток будет одновременно выполнять операторы синхронизированного блока, и, таким образом, предотвращает повреждение несколькими потоками совместно используемых данных, присутствующих внутри блока.
synchronized : метод
Общий синтаксис написания синхронизированного метода:
Здесь lockObject – это просто ссылка на объект, чья блокировка связана с монитором, который представляет синхронизированные операторы.
Подобно синхронизированному блоку, поток должен получить блокировку на подключенном объекте монитора с помощью синхронизированного метода. В случае синхронизированного метода объект блокировки:
Java синхронизированное ключевое слово является реентерабельным по своей природе. Это означает, что если синхронизированный метод вызывает другой синхронизированный метод, который требует такой же блокировки, то текущий поток, который удерживает блокировку, может войти в этот метод без получения блокировки.
BestProg
Синхронизация. Монитор. Общие понятия. Ключевое слово synchronized
Содержание
Поиск на других ресурсах:
1. Понятие синхронизации между потоками. Необходимость применения синхронизации. Монитор
Бывают случаи, когда два или более параллельно-выполняемых потока пытаются обратиться к общему ресурсу. Если ресурс может быть изменен в результате выполнения одного из потоков, то другие потоки должны дождаться пока изменения в потоке будут завершены. В противном случае, потоки получат ресурс, данные которого будут ошибочными.
Гарантировать одновременное использование общего ресурса только одним потоком может так называемая синхронизация. Значит, синхронизация — это процесс, который упорядочивает доступ из разных потоков к общему ресурсу.
Синхронизация базируется на использовании мониторов. Монитор — это объект, который используется для взаимоисключающей блокировки. Взаимоисключающая блокировка позволяет владеть монитором только одному объекту-потоку. Каждый объект-поток имеет собственный, неявно связанный с ним, монитор.
Поток выполнения (который представлен объектом) может завладеть монитором в случае, если он запросил блокировку и монитор свободен на данный момент. После того, как объект вошел в монитор, все остальные объекты-потоки, пытающиеся войти в монитор, приостанавливаются и ожидают до тех пор, пока первый объект не выйдет из монитора.
Монитором может обладать только один поток. Если поток (объект) обладает монитором, то он при необходимости может повторно войти в него.
В языке Java синхронизация применяется к целым методам или фрагментам кода. Исходя из этого существует два способа синхронизации программного кода:
Модификатор доступа synchronized применяется при объявлении синхронизированного метода и имеет следующую общую форму:
3. Оператор synchronized() < >. Общая форма
Общая форма оператора synchronized () следующая:
4. Пример, демонстрирующий синхронизированный доступ к общему методу из трех разных потоков. Применение модификатора доступа synchronized
В примере демонстрируется необходимость применения модификатора доступа synchronized с целью упорядочения доступа к ресурсу из разных потоков.
Результат выполнения программы
Если в вышеприведенном примере перед методом Get() класса Array5 убрать ключевое слово synchronized
то последовательного выполнения потоков не будет. В этом случае программа после каждого запуска будет выдавать разный (хаотический) результат, например следующий
5. Пример использования оператора synchronized() <> для синхронизированного доступа к общему ресурсу
После внесенных изменений, сокращенный код программы будет следующий:
Что такое синхронизация в Java?
Введение в синхронизацию в Java
Зачем нам нужна синхронизация в Java?
Что такое состояние гонки?
Когда два или более потоков работают параллельно, они имеют тенденцию получать доступ и изменять общие ресурсы в этот момент времени. Последовательности, в которых выполняются потоки, определяются алгоритмом планирования потока.
Из-за этого нельзя предсказать порядок, в котором будут выполняться потоки, поскольку он контролируется только планировщиком потоков. Это влияет на вывод кода и приводит к противоречивым выводам. Поскольку множество нитей участвуют в гонке друг с другом для завершения операции, условие называется «условием гонки».
Например, давайте рассмотрим следующий код:
Class Modify:
package JavaConcepts;
public class Modify implements Runnable(
private int myVar=0;
public int getMyVar() (
return myVar;
)
public void setMyVar(int myVar) (
this.myVar = myVar;
)
public void increment() (
myVar++;
)
@Override
public void run() (
// TODO Auto-generated method stub
this.increment();
System.out.println(«Current thread being executed «+ Thread.currentThread().getName() + «Current Thread value » + this.getMyVar());
)
)
Class RaceCondition:
package JavaConcepts;
public class RaceCondition (
public static void main(String() args) (
Modify mObj = new Modify();
Thread t1 = new Thread(mObj, «thread 1»);
Thread t2 = new Thread(mObj, «thread 2»);
Thread t3 = new Thread(mObj, «thread 3»);
t1.start();
t2.start();
t3.start();
)
)
При последовательном запуске приведенного выше кода выходные данные будут следующими:
Ourput1:
Текущий поток выполняется поток 1 Текущее значение потока 3
Текущий поток выполняется поток 3 Текущее значение потока 2
Текущий поток выполняется поток 2 Текущее значение потока 3
Выход2:
Текущий поток выполняется поток 3 Текущее значение потока 3
Текущий поток выполняется поток 2 Текущее значение потока 3
Текущий поток выполняется поток 1 Текущее значение потока 3
output3:
Текущий поток выполняется поток 2 Текущее значение потока 3
Текущий поток выполняется поток 1 Текущее значение потока 3
Текущий поток выполняется поток 3 Текущее значение потока 3
Output4:
Текущий поток выполняется поток 1 Текущее значение потока 2
Текущий поток выполняется поток 3 Текущее значение потока 3
Текущий поток выполняется поток 2 Текущее значение потока 2
Выход в этом случае:
Текущий поток выполняется поток 1 Текущее значение потока 1
Это означает, что когда запущен один поток, результат будет таким, как ожидалось. Однако, когда запущено несколько потоков, значение изменяется каждым потоком. Следовательно, необходимо ограничить количество потоков, работающих над общим ресурсом, одним потоком за раз. Это достигается с помощью синхронизации.
Понимание того, что такое синхронизация в Java
Как Синхронизация в Java работает внутри?
Давайте синхронизируем наш предыдущий пример, синхронизируя код внутри метода run, используя синхронизированный блок в классе «Modify», как показано ниже:
Class Modify:
package JavaConcepts;
public class Modify implements Runnable(
private int myVar=0;
public int getMyVar() (
return myVar;
)
public void setMyVar(int myVar) (
this.myVar = myVar;
)
public void increment() (
myVar++;
)
@Override
public void run() (
// TODO Auto-generated method stub
synchronized(this) (
this.increment();
System.out.println(«Current thread being executed »
+ Thread.currentThread().getName() + » Current Thread value » + this.getMyVar());
)
)
)
Код для класса «RaceCondition» остается прежним. Теперь при запуске кода вывод выглядит следующим образом:
Output1:
Текущий поток выполняется поток 1 Текущее значение потока 1
Текущий поток выполняется поток 2 Текущее значение потока 2
Текущий поток выполняется поток 3 Текущее значение потока 3
Выход2:
Текущий поток выполняется поток 1 Текущее значение потока 1
Текущий поток выполняется поток 3 Текущее значение потока 2
Текущий поток выполняется поток 2 Текущее значение потока 3
Обратите внимание, что наш код обеспечивает ожидаемый результат. Здесь каждый поток увеличивает значение на 1 для переменной «myVar» (в классе «Modify»).
Примечание. Синхронизация требуется, когда несколько потоков работают на одном объекте. Если несколько потоков работают с несколькими объектами, синхронизация не требуется.
Например, давайте изменим код в классе «RaceCondition», как показано ниже, и поработаем с ранее несинхронизированным классом «Modify».
package JavaConcepts;
public class RaceCondition (
public static void main(String() args) (
Modify mObj = new Modify();
Modify mObj1 = new Modify();
Modify mObj2 = new Modify();
Thread t1 = new Thread(mObj, «thread 1»);
Thread t2 = new Thread(mObj1, «thread 2»);
Thread t3 = new Thread(mObj2, «thread 3»);
t1.start();
t2.start();
t3.start();
)
)
Выход:
Текущий поток выполняется поток 1 Текущее значение потока 1
Текущий поток выполняется поток 2 Текущее значение потока 1
Текущий поток выполняется поток 3 Текущее значение потока 1
Типы синхронизации в Java:
Координация 2.Thread (межпотоковое общение в Java)
Взаимоисключающий:
я. Синхронизированный метод: мы можем использовать ключевое слово «synchronized» для метода, таким образом делая его синхронизированным методом. Каждый поток, который вызывает синхронизированный метод, получит блокировку для этого объекта и освободит его, как только его операция будет завершена. В приведенном выше примере мы можем сделать наш метод run () синхронизированным с помощью ключевого слова synchronized после модификатора доступа.
@Override
public synchronized void run() (
// TODO Auto-generated method stub
this.increment();
System.out.println(«Current thread being executed »
+ Thread.currentThread().getName() + » Current Thread value » + this.getMyVar());
)
Выход для этого случая будет:
Текущий поток выполняется поток 1 Текущее значение потока 1
Текущий поток выполняется поток 3 Текущее значение потока 2
Текущий поток выполняется поток 2 Текущее значение потока 3
II. Статический синхронизированный метод: для синхронизации статических методов необходимо получить блокировку на уровне класса. После того, как поток получит блокировку уровня класса только тогда, он сможет выполнять статический метод. Пока поток удерживает блокировку уровня класса, никакой другой поток не может выполнить любой другой статический синхронизированный метод этого класса. Однако другие потоки могут выполнять любой другой обычный метод или обычный статический метод или даже нестатический синхронизированный метод этого класса.
Например, давайте рассмотрим наш класс «Modify» и внесем в него изменения путем преобразования нашего метода «increment» в статический синхронизированный метод. Изменения кода, как показано ниже:
package JavaConcepts;
public class Modify implements Runnable(
private static int myVar=0;
public int getMyVar() (
return myVar;
)
public void setMyVar(int myVar) (
this.myVar = myVar;
)
public static synchronized void increment() (
myVar++;
System.out.println(«Current thread being executed » + Thread.currentThread().getName() + » Current Thread value » + myVar);
)
@Override
public void run() (
// TODO Auto-generated method stub
increment();
)
)
III. Синхронизированный блок. Одним из основных недостатков синхронизированного метода является то, что он увеличивает время ожидания потоков, влияя на производительность кода. Следовательно, чтобы иметь возможность синхронизировать только необходимые строки кода вместо всего метода, необходимо использовать синхронизированный блок. Использование синхронизированного блока сокращает время ожидания потоков, а также повышает производительность. В предыдущем примере мы уже использовали синхронизированный блок при первой синхронизации нашего кода.
Пример:
public void run() (
// TODO Auto-generated method stub
synchronized(this) (
this.increment();
System.out.println(«Current thread being executed »
+ Thread.currentThread().getName() + » Current Thread value » + this.getMyVar());
)
)
Координация нити:
Для синхронизированных потоков связь между потоками является важной задачей. Встроенные методы, которые помогают достичь межпотокового взаимодействия для синхронизированного кода, а именно:
Примечание. Эти методы относятся к классу объектов, а не к классу потоков. Чтобы поток мог вызывать эти методы для объекта, он должен удерживать блокировку этого объекта. Кроме того, эти методы заставляют поток снимать блокировку с объекта, к которому он вызывается.
wait (): поток при вызове метода wait () снимает блокировку объекта и переходит в состояние ожидания. У него есть две перегрузки метода:
notify (): поток отправляет сигнал другому потоку в состоянии ожидания, используя метод notify (). Он отправляет уведомление только одному потоку, так что этот поток может возобновить свое выполнение. Какой поток получит уведомление среди всех потоков в состоянии ожидания, зависит от виртуальной машины Java.
notifyAll (): когда поток вызывает метод notifyAll (), каждый поток в своем состоянии ожидания уведомляется. Эти потоки будут выполняться один за другим в соответствии с порядком, определенным виртуальной машиной Java.
Вывод
В этой статье мы увидели, как работа в многопоточной среде может привести к несогласованности данных из-за состояния гонки. Как синхронизация помогает нам преодолеть это, ограничивая один поток для одновременной работы с общим ресурсом. Также как синхронизированные потоки общаются друг с другом.