Синхронизация. Монитор. Общие понятия. Ключевое слово synchronized
Содержание
- 1. Понятие синхронизации между потоками. Необходимость применения синхронизации. Монитор
- 2. Модификатор доступа synchronized. Общая форма
- 3. Оператор synchronized() { }. Общая форма
- 4. Пример, демонстрирующий синхронизированный доступ к общему методу из трех разных потоков. Применение модификатора доступа synchronized
- 5. Пример использования оператора synchronized() {} для синхронизированного доступа к общему ресурсу
- Связанные темы
Поиск на других ресурсах:
1. Понятие синхронизации между потоками. Необходимость применения синхронизации. Монитор
Бывают случаи, когда два или более параллельно-выполняемых потока пытаются обратиться к общему ресурсу. Если ресурс может быть изменен в результате выполнения одного из потоков, то другие потоки должны дождаться пока изменения в потоке будут завершены. В противном случае, потоки получат ресурс, данные которого будут ошибочными.
Гарантировать одновременное использование общего ресурса только одним потоком может так называемая синхронизация. Значит, синхронизация — это процесс, который упорядочивает доступ из разных потоков к общему ресурсу.
Синхронизация базируется на использовании мониторов. Монитор — это объект, который используется для взаимоисключающей блокировки. Взаимоисключающая блокировка позволяет владеть монитором только одному объекту-потоку. Каждый объект-поток имеет собственный, неявно связанный с ним, монитор.
Поток выполнения (который представлен объектом) может завладеть монитором в случае, если он запросил блокировку и монитор свободен на данный момент. После того, как объект вошел в монитор, все остальные объекты-потоки, пытающиеся войти в монитор, приостанавливаются и ожидают до тех пор, пока первый объект не выйдет из монитора.
Монитором может обладать только один поток. Если поток (объект) обладает монитором, то он при необходимости может повторно войти в него.
В языке Java синхронизация применяется к целым методам или фрагментам кода. Исходя из этого существует два способа синхронизации программного кода:
- за счет использования модификатора доступа synchronized;
- за счет использования оператора synchronized () {}.
⇑
2. Модификатор доступа synchronized. Общая форма
Модификатор доступа synchronized применяется при объявлении синхронизированного метода и имеет следующую общую форму:
synchronized return_type MethodName(parameters) {
...
}
здесь
- MethodName – имя метода, который есть общим ресурсом и его нужно синхронизировать;
- return_type -тип, который возвращает метод;
- parameters – параметры метода.
⇑
3. Оператор synchronized() { }. Общая форма
Оператор synchronized(), в отличие от модификатора доступа synchronized, используется для применения синхронизации к объектам (методам), которые изначально при их разработке не предназначались для многопоточного доступа. То есть, в классе не реализованы методы с модификатором доступа synchronized (классы сторонних разработчиков).
Общая форма оператора synchronized () следующая:
synchronized (reference) { // Операторы, которые нужно синхронизировать // ... }
здесь
- reference – ссылка на синхронизируемый объект;
- фигурные скобки {}, которые определяют блок синхронизации. В этом блоке указываются операторы для синхронизации.
⇑
4. Пример, демонстрирующий синхронизированный доступ к общему методу из трех разных потоков. Применение модификатора доступа synchronized
В примере демонстрируется необходимость применения модификатора доступа synchronized с целью упорядочения доступа к ресурсу из разных потоков.
Общим ресурсом является метод Get() класса Array5. Метод Get() возвращает массив из 5 чисел, значения которых последовательно изменяются от 1 к 5. К этому методу будет осуществлена попытка одновременного доступа из разных потоков, поэтому метод обозначен как synchronized.
Инкапсуляция одного потока выполнения осуществляется в классе ArrayThread классическим (стандартным) способом с помощью реализации интерфейса Runnable. Класс ArrayThread содержит следующие общедоступные (public) элементы:
- A — ссылку на экземпляр класса Array5. С помощью этой ссылки происходит доступ к методу Get();
- AI — массив чисел, получаемый из метода Get();
- t — ссылку на текущий поток;
- конструктор ArrayThread(), который получает экземпляр класса Array5 и название потока. В конструкторе происходит создание потока выполнения и его запуск;
- метод run(), вызывающий метод Get() с помощью ссылки A.
В классе Threads представлена функция main(), в которой создается три потока AT1, AT2, AT3 типа ArrayThread и один объект A5. Метод Get() объекта A5 является общим ресурсом для этих трех потоков.
// Тема. Синхронизация потоков. // Класс, содержащий общий ресурс class Array5 { // Синхронизированный метод, возвращающий массив з 5 чисел // и печатающий его на экране synchronized public int[] Get(String threadName) { // Вывести название выполняемого потока System.out.println("Begin => " + threadName); // Сформировать 5 целых чисел от 1 до 5 int[] AI = new int[5]; for (int i=0; i<AI.length; i++) AI[i] = i+1; // Вызвать задержку,чтоб другие потоки могли выполняться try { Thread.sleep(100); } catch (InterruptedException e) { System.out.println(e.getMessage()); } // Вывести в методе массив AI для контроля System.out.print(threadName + " = [ "); for (int i=0; i<AI.length; i++) System.out.print(AI[i] + " "); System.out.println(" ]"); // Оповестить об окончании выполнения метода System.out.println("End => " + threadName); return AI; } } // Класс, который является потоком читающим массив чисел class ArrayThread implements Runnable { Array5 A; // экземпляр общего ресурса int[] AI; // массив из 5 чисел, который получается из общего ресурса Thread t; // ссылка на текущий поток // Конструктор - получает параметры: // - имя потока; // - экземпляр класса Array5. ArrayThread(String threadName, Array5 A) { // Сохранить массив this.A = A; // Создать поток t = new Thread(this, threadName); // Запустить поток на выполнение t.start(); // вызов метода run() } // Метод, в котором запускается поток, создающий массив из 5 чисел public void run() { AI = A.Get(t.getName()); // получить массив чисел от 1 до 5 } } public class Threads { public static void main(String[] args) { // Продемонстрировать доступ к общему ресурсу из 3-х разных потоков // 1. Объявить экземпляр, который будет общим ресурсом в трех разных потоках Array5 A5 = new Array5(); // 2. Объявить три разных потока и запустить их ArrayThread AT1 = new ArrayThread("AT1", A5); ArrayThread AT2 = new ArrayThread("AT2", A5); ArrayThread AT3 = new ArrayThread("AT3", A5); // 3. Дождаться завершения всех трех потоков try { AT1.t.join(); AT2.t.join(); AT3.t.join(); } catch (InterruptedException e) { System.out.println(e.getMessage()); } } }
Результат выполнения программы
Begin => AT1 AT1 = [ 1 2 3 4 5 ] End => AT1 Begin => AT3 AT3 = [ 1 2 3 4 5 ] End => AT3 Begin => AT2 AT2 = [ 1 2 3 4 5 ] End => AT2
Если в вышеприведенном примере перед методом Get() класса Array5 убрать ключевое слово synchronized
class Array5 { ... // здесь нет модификатора synchronized (тут вам не здесь:) public int[] Get(String threadName) { ... } }
то последовательного выполнения потоков не будет. В этом случае программа после каждого запуска будет выдавать разный (хаотический) результат, например следующий
Begin => AT1 Begin => AT2 Begin => AT3 AT1 = [ AT2 = [ 1 1 2 2 3 3 4 4 5 ] End => AT1 5 ] End => AT2 AT3 = [ 1 2 3 4 5 ] End => AT3
⇑
5. Пример использования оператора synchronized() {} для синхронизированного доступа к общему ресурсу
Код предыдущего примера (смотрите п. 4), синхронизирующего потоки, можно представить по-другому. В этом случае, вместо модификатора доступа synchronized перед именем метода Get() нужно использовать оператор synchronized () {}. Оператор synchronized должен быть использован в методе run() класса ArrayThread.
После внесенных изменений, сокращенный код программы будет следующий:
// Класс, который есть общим ресурсом class Array5 { // Метод, возвращающий массив из 5 чисел и выводящий его на экран. // Уже не синхронизирован public int[] Get(String threadName) { ... } } // Класс, который есть потоком читающим массив чисел class ArrayThread implements Runnable { Array5 A; // экземпляр общего ресурса int[] AI; // массив из 5 чисел, получающийся из общего ресурса Thread t; // ссылка на текущий поток // Конструктор - получает параметры: // - имя потока; // - экземпляр класса Array5. ArrayThread(String threadName, Array5 A) { ... } // Метод, в котором запускается поток, создающий массив из 5 чисел public void run() { // Оператор synchronized для общего ресурса A synchronized (A) { AI = A.Get(t.getName()); // получить массив чисел от 1 до 5 } } }
⇑
Связанные темы
- Многозадачность. Потоки выполнения. Основные понятия
- Средства Java для работы с потоками выполнения. Класс Thread. Интерфейс Runnable. Главный поток выполнения. Создание дочернего потока
- Методы класса Thread: getName(), run(), start(), sleep(). Примеры
- Методы класса Thread: join(), isAlive(), getPriority(), setPriority(). Примеры
- Взаимодействие между приложениями. Методы wait(), notify(), notifyAll()
- Состояния потока выполнения. Метод getState(). Пример
⇑