Главная » Java » Инструкции synchronized Java

0

Инструкции synchronized позволяют выполнять синхронизированный код, который способен блокировать произвольный объект, а не только текущий, либо уменьшить длительность блокировки, распространяя ее влияние только на часть метода. Инструкция synchronized состоит из двух частей – ссылки на объект, блокировка которого запрашивается, и фрагмента кода, выполняемого после З:1хвата блокировки. Общая синтаксическая форма synchronized -инструкции вы-

глядит так:

synchronized (выражение) {

Инструкции

}

выражение в результате вычисления должно давать объектную ссылку. После получения права блокировки выполняется блок кода Инструкции, по завершении которого блокировка освобождается, – блокировка снимается даже в том случае, когда внутри фрагмента кода  Инструкции выбрасывается исключение, не подвергающееся обработке. Объявление synchronized -метода, рассмотренное выше, – это, на самом деле, сокращенная синтаксическая конструкция для описания метода, тело которого заключено внутрь synchronized -инструкции, использующей для указания на блокируемый объект ссылку this.

Рассмотрим метод, предусматривающий замену значения каждого элемента массива абсолютной величиной и основанный на использовании инструкции synchronized, которая регулирует доступ к массиву:

/** Присвоить элементам целочисленного массива абсолютные величины текущих значений */

public static void abs(int[] values) {

synchronized (values) {

for (int i = о; i < values.length; i++)

{

if (values[i] < 0)

values[i] = -values[i];

            }

}

}

Массив values содержит значения, подлежащие изменению. Мы синхронизируем доступ к массиву, указывая идентификатор values в заголовке инструкции synchronized. Теперь можно гарантировать, что во время выполнения внутреннего цикла никакой иной фрагмент кода, помеченный признаком synchronized, не сможет изменить содержимое элементов массива values. ЭТО пример того, что обычно называют синхронизацией на стороне клиента (clientside synchronization), – все клиенты, совместно использующие объект (в нашем случае, массив), обязуются синхронизировать свои действия по отношению к объекту прежде, чем предпринимать попытки манипуляций им. Других способов защиты совместно используемых объектов, подобных массивам, от опасности единовременного доступа не существует, поскольку они не обладают соответствующими методами, которые можно было бы обозначить модификатором synchronized.

Инструкции synchronized обладают целым рядом специальных областей применения и преимуществ по сравнению с synchronized -методами. вопервых они дают возможность определения синхронизированного участка кода, охватывающего только некоторый фрагмент тела метода. Не надо забывать, что синхронизация оказывает влияние на производительность кода – пока один поток владеет блокировкой, остальные вынуждены ожидать своей очереди, – и поэтому общее правило многопоточного программирования гласит, что блокировка должна охватывать настолько короткий фрагмент кода (и период времени его выполнения), насколько это возможно. Используя инструкцию synchronized, мы можем обращаться к средствам блокирования объекта только тогда, когда это совершенно необходимо. Например, метод, Выполняющий сложные вычисления с последующим присваиванием результатов полям объекта, зачастую нуждается в синхронизации только той части кода, которая непосредственно ответственна за операции присваивания, а не за весь длительный процесс вычислений в целом. Во-вторых, synchronized -инструкции позволяют синхронизировать объекты, отличные от thi5, и дают возможность создавать самые разнообразные схемы синхронизации. Одна из достаточно часто встречающихся ситуаций связана с необходимостью обеспечения более высокого уровня интенсивности конкурентного доступа к коду класса за счет уменьшения размеров блокируемых областей кода. Может случиться и так, что различные группы методов класса работают с разными данными того же класса и в то время как внутри группы синхронизация доступа необходима, взаимная связь между группами отсутствует и синхронизация на этом уровне не нужна. Вместо того чтобы обозначать модификатором synchronized все методы класса, мы можем определить отдельные объекты, подлежащие блокированию в каждой группе методов, и снабдить методы соответствующими инструкциями synchronized. Рассмотрим пример:

class SeparateGroups {

 private double aVal = 0.0;

 private double bVal = 1.1;

 protected Object lockA = new Object();

 protected Object lockB = new Object();

 

public double getA() {

synchronized (lockA){

            return aVal;

}

}

 

public vois setA(double val) {

synchronized (lockA){

            aVal = val;

}

}

 

public double getB() {

synchronized (lockB){

            return bVal;

}

}

 

public vois setB(double val) {

synchronized (lockB){

            bVal = val;

}

}

}

public void reset() {

 synchronized  (lockA) {

 synchronized  (lockB) {

 aVal = bVal = 0.0;

}

}

}

}

Две ссылочные переменные, указывающие на блокируемые объекты, объявлены как protected, чтобы расширенные классы смогли корректно Синхронизировать свои собственные методы. Обратите внимание и на то, что метод reset, инициализирующий значения jaVal и bVal, прежде запрашивает право на блокирование обоих объектов, lockA и 1ekB.

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

public class Outer {

private int data;

// …

private class Inner {

void setOuterData() {

synchronized  (outer.this) {

data = 12;

}

}

}

}

Внутренний объект, как и любой другой объект, синхронизируется независимо – запрос на блокировку внутреннего объекта не оказывает влияния на состояние внешнего объекта точно так же, как и обратное действие. Внутренний класс, которому необходимо синхронизировать доступ к коду внешнего объекта, должен выполнить эту операцию явно, и инструкция synchronized – лучшее средство для достижения подобной цели (альтернативой может быть только объявление в составе внешнего класса специального синхронизированного метода, предназначенного для вызова из тела внутреннего класса).

Если необходимо, чтобы synchronized -инструкция пользовал ась той же блокировкой, что и статические синхронизированные методы класса, для задания блокируемого объекта можно использовать литерал типа class для текущего Класса. Такой подход применим и в том случае, если надлежит предотвратить доступ к статическим данным со стороны нестатического кода. Вновь рассмотрим пример класса Body. В его составе есть статическое поле, NextID, предназначенное для хранения очередного свободного номера, готового для присваивания новому объекту Body. К полю обращается конструктор класса Body без аргументов. Если допустить возможность конкурентного создания объектов Body, при обновлении содержимого поля nextID могут возникнуть недоразумения, связанные с одновременным доступом к нему со стороны нескольких потоков.

Чтобы предупредить подобное поведение программы, поместим внутрь конструктора инструкцию synchronized, запрашивающую право на блокировку объекта вody. class:

Body() {

synchronized  (Body.class) {

idNum = nextID++;

}

}

Инструкция synchronized в теле конструктора захватывает блокировку объекта типа class для объекта Body точно так же, как это делает статический synchronized -метод класса. Было бы неверным использовать для синхронизации ссылку this, поскольку при каждом вызове конструктора она указывает на разные объекты, поэтому блокировка текущего (this) объекта не способна предотвратить возможность одновременного доступа к nextID из нескольких потоков. Также неправильно для получения ссылки на объект типа class для текущего экземпляра использовать метод Object. getclass – в расширенном классе, таком как AttributedBody, он возвратит ссылку на объект class для AttributedBody, но не Body, и вновь вместо одной блокировки будет создано несколько и единовременный доступ к данным окажется вполне возможным. Существует простое правило: следует всегда защищать статические данные с помощью блокировки объекта типа class для класса, в котором эти данные объявлены.

Во многих случаях для защиты кода вместо инструкций synchronized удобнее, тем не менее, использовать synchronized -методы. (Например, в классе Body мы могли бы заключить код, который обращается к полю nextID, внутри статического синхронизированного метода getNextID.) Ответить на вопрос, когда применять один подход, а когда – другой, вам помогут только собственные знания и практический опыт.

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

Источник: Арнолд, Кен, Гослинг, Джеймс, Холмс, Дэвид. Язык программирования Java. 3-е изд .. : Пер. с англ. – М. : Издательский дом «Вильяме», 2001. – 624 с. : ил. – Парал. тит. англ.

По теме:

  • Комментарии