Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Медведев_С++_CLI_C#_Java_J#.doc
Скачиваний:
3
Добавлен:
01.07.2025
Размер:
5 Мб
Скачать
☆

9.6.1. Операторы lock и synchronized

Операторы synchronized языка __________Java и lock языка C# позволяют пред-

ставить любой оператор программы в виде критической секции, которая свя-

зывается с некоторым объектом и может выполняться только одним пото-

ком среди потоков, имеющих доступ к этому объекту. Выполнение других

потоков, пытающихся воспользоваться этим объектом, приостанавливается,

пока указанный оператор не выполнится, освободив тем самым этот объект

от своего монопольного использования. То есть операторы lock (lock - бло-

кировать) и synchronized (synchronized - синхронизирован) захватывают (бло-

кируют) объект, на который они ссылается, на время выполнения указанного

оператора, и освобождает его, как только оператор выполнится.

Операторы synchronized языка Java и lock языка C# имеют следующую

грамматику:

synchronized (object) оператор // Java

lock (object) оператор // C#

где object - ссылка на объект, который нужно синхронизировать,

оператор - оператор, реализующий критическую секцию.

Ссылаемый объект (разделяемый ресурс) используется одновременно

несколькими потоками. Если этот объект используется некоторым потоком,

то он блокируется от использования другими потоками. Часто в качестве

ссылки на объект применяется ключевое слово this, если предполагается

употребить критическую секцию применительно к объекту, к которому она

принадлежит. Как известно, обращение к статическим данным и функциям

осуществляется через класс, в котором они размещены.

Поскольку обычно к блокируемому объекту применяется не один, а по-

следовательность из нескольких операторов, то в качестве оператора, сле-

дующем за ключевыми словами synchronized и lock, используют составной

оператор, включающий требуемую последовательность операторов в фигур-

ных скобках. Эта последовательность операторов представляет собой крити-

ческую секщно, которая защищена операторами synchronized и lock и являет-

ся единственным владельцем процессора на время своего выполнения с объ-

ектом.

Пример 9.6.1.1. Оператор lock и потоки на языке C#.

///////////////

/ / c #

using System;

using System.Threading;

class Obj // Класс разделяемого объекта

{

private int count= 0; // Счётчик

public int Count // Свойство Count

{

set {count= value;}

get {return count;> >

>

class User // Класс объектов с

{

private Obj obj;

int num;

Thread thr;

потоками

// Ссылка на разделяемый объект

// Номер объекта

// Ссылка на поток

// Конструктор

public User (Obj obj, int num)

{

this.obj= obj;

thls.num= num;

// Создать и стартовать поток

thr= new Thread(new ThreadStart(this.ThrFunc));

thr.Start();

// Потоковая функция

private void ThrFunc ()

{

lock(obJ) // 1

{

while (obJ.Count < 5)

{

//lock(obj) // Блокировать разделяемый объект//2

{

obj.Count= obj.Count +1; // Увеличить счётчик

Thread.Sleep (1); // Поспать

Console.WrlteLine("User" + num + " count= "

+ obj.Count);

} >

Console.WriteLine("Thread of User" + num +

" flnished");

>

>

>

class LockTest // Касс с основным потоком

{

static vold Maln ( )

{

Obj obj= new Obj (); // Создать разделяемый объект

User usl= new User (obj, 1); // Создать первый объект с потоком

User us2= new User (obj, 2); // Создать второй объект с потоком

Console.WriteLine ("OKl"); // Создали объекты

Thread.Sleep (100);

Console.WriteLine ("OK2"); // Завершился основной поток

>

/>*

Result: Вариант А. Оператор lock отсутствует (Строки 1 и 2

закомментированы).

OKl

Userl count= 1

User2 count= 1

Userl count= 3

User2 count= 3

Userl count= 5

Thread of Userl finished

User2 count= 5

Thread of User2 finished

OK2

Result: Вариант Б. Оператор lock внутри цикла while охватывает блок,

включающий увеличение счетчика, функции Sleep и WriteLine

(строка 1 закомментирована, а строка 2 не закомментирована).

OKl

Userl count= 1

User2 count= 2

Userl count= 3

User2 count= 4

Userl count= 5

Thread of Userl finished

User2 count= 6

Thread of User2 finished

OK2

Result: Вариант В. Оператор lock охватывает оператор while

(строка 1 не закомментирована, а строка 2 закомментирована).

OKl

Userl count= 1

Userl count= 2

Userl count= 3

Userl count= 4

Userl count= 5

Thread of Userl finished

Thread of User2 finished

OK2

*/

В примере 9.6.1.1 класс Obj описываеттип объекта, доступ к которому

осуществляется несколькими потоками, которые изменяют значение счётчи-

ка count и считывают это значение. Доступ к счётчику осуществляется по-

средством свойства Count.

Класс User описывает тип объектов, каждый из которых содержит поток,

осуществляющий увеличение счётчика разделяемого объекта класса Obj на 1

и выдачу полученного значения на консоль. Потоковая функция перед выда-

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

полнить и другие какие либо операции с разделяемым объектом. В течение

выполнения потоковой функции другие потоки могут изменить значение

счётчика, если доступ к используемому разделяемому объекту не синхрони-

зировать - это будет показано ниже.

Выполнение потоковой функции состоит в пятикратном обращении к разде-

ляемому объекту obj класса Obj. При каждом обращении разделяемый объект

блокируется оператором lock с составным оператором (критической секци-

ей), осуществляющим увеличение счётчика, задержку и выдачу на консоль.

Функция Main() класса LockTest создаёт объект obj класса Obj и объ-

екты usl и us2 класса User, потоки которых используют объект obj.

При создании объектов us 1 и us2 их конструкторы устанавливают но-

мера этим объектам и фиксируют ссылки на разделяемый объект obj. Затем

создаются и запускаются их потоки.

Рассмотрим несколько вариантов выполнения этой программы.

Вариант А. Созданы два объекта usl и us2 с потоками. Синхронизация па-

раллельного выполнения потоков отсутствует, оператор lock не использует-

ся. Обращение к разделяемому объекту obj класса Obj не управляется, по-

этому результат изменения счётчика не предсказуем. Он может быть как в

примере 9.6.1.1, но может и отличаться в зависимости от производительности

процессора.

Вариант Б. Созданы два объекта usl и us2 с потоками и их потоки запуще-

ны. Оператор lock охватывает нутро цикла while, позволяя только одному по-

току менять значение счётчика count разделяемого объекта obj. Результат ра-

боты программы показывает, что, действительно, объект obj разделяется ме-

жду потоками. При изменении значения счетчика count некоторым потоком,

этот поток захватывает объект obj, не позволяя другому потоку также изме-

ПОТ1 ?uaupuTjp pujirouva pnnnt lA'1 nMuntTwrg mfifm., nT,nrnci***jr4 лиггцл итл . u . . u w . . w . W 1 1 I .W V . w . Ц . и и V W U . . . > . ^ J j^ ~ ' J J " 1 " * " . " ^uvu.ut u^wi ^wuuiAM U ( ^ i l v , i . W

значение счетчика count последовательно меняется потоком каждого объекта

класса User, поскольку после обращения к разделяемому объекту с использо-

ванием оператора lock поток выходит из оператора lock, позволив другому

потоку на один цикл его потоковой функции захватить разделяемый объект.

Итак, не мешая друг другу, потоки раздельно используют разделяемый

объект obj, увеличивая последовательно значение его счётчика на 1. Благо-

даря оператору lock поток каждого объекта захватывает разделяемый объ-

ект, исключив доступ к нему со стороны другого потока

Вариант В. Созданы два объекта usl и us2 с потоками и их потоки запуще-

ны. Оператор lock теперь охватывает оператор while, позволяя только одному

потоку изменять значение счётчика count разделяемого объекта obj до завер-

шения циклов while. Результат работы программы показывает, что, действи-

тельно, объект obj захватывается одним потоком.

По результату работы варианта В возникает вопрос: почему же после

завершения потока объекта usl не появились строки, указывающие о выпол-

нении циклов извлечённого из очереди ожидающих потоков потока объекта

us2? Почему появилась строка 'Thread of User2 finished"? Дело в том, что за-

вершившийся поток объекта usl увеличил значение счетчика до 5, поэтому

цикл while потока объекта us2 не выполнялся, и посему сразу появилось со-

общение об его завершении. Стоит вставить в конце потоковой функции опе-

ратор

obj.setCount (0);

и ситуация изменится, поток объекта us2 оживёт.

Использование оператора synchronized языка Java, аналогичное приме-

нению lock в языке C#, приведено в примере 9.6.1.2.

Пример 9.6.1.2. Оператор synchronized и потоки на языке Java.

///////////////

// Java и J#

class Obj // Класс разделяемого объекта

{

private lnt count= 0; // Счётчик

/p/u bСlвicо йvсoтidв о sCeotuCnotu nt(lnt Count) {count= Count;>

public int getCount ( ) <roturn count;>

}

class User extends Thread // Класс объектов с потоками

{

private Obj obj; // Ссылка на разделяемый объект