Философия Java3
Шрифт:
//К такому коду доступ может получить // одновременно только один поток
}
Такая конструкция иначе называется синхронизированной блокировкой (synchronized block); перед входом в нее необходимо получить блокировку для syncObject. Если блокировка уже предоставлена другому потоку, вход в последующий фрагмент кода запрещается до тех пор, пока блокировка не будет снята.
Следующий пример сравнивает два подхода к синхронизации, показывая, насколько увеличивается время, предоставляемое потокам для доступа к объекту при использовании синхронизированной блокировки вместо синхронизации
//: concurrency/CriticalSection.java
// Синхронизация блоков вместо целых методов. Также демонстрирует защиту
// неприспособленного к многопоточности класса другим классом
package concurrency;
import java.util.concurrent.*:
import java.util.concurrent.atomic.*;
import java.util.*;
class Pair { // Not thread-safe private int x, y; public Pair(int x. int y) { this.x = x; this.у = у;
}
public PairO { this(0, 0); } public int getXO { return x; } public int getYO { return y; } public void incrementXO { x++; } public void incrementYO { y++; } public String toStringO {
return "x; " + x + ", y; " + y;
}
public class PairValuesNotEqualException extends RuntimeException {
public Pai rValuesNotEqual Excepti onO {
superC'Pair values not equal; " + Pair.this);
}
}
// Произвольный инвариант - обе переменные должны быть равны; public void checkStateO { if(x != у)
throw new PairValuesNotEqualException;
}
}
// Защита класса Pair внутри приспособленного к потокам класса; abstract class PairManager {
Atomiclnteger checkCounter = new AtomicInteger(O). protected Pair p = new PairO. private List<Pair> storage =
Collections synchronizedList(new ArrayList<Pair>0). public synchronized Pair getPairO {
// Создаем копию, чтобы сохранить оригинал в безопасност return new Pair(p getXO, p getYO).
}
// Предполагается, что операция занимает некоторое время protected void store(Pair р) { storage add(p), try {
TimeUnit MILLISECONDS sleep(50); } catch(InterruptedException ignore) {}
}
public abstract void incrementO.
}
// Синхронизация всего метода.
class PairManagerl extends PairManager { public synchronized void incrementO { p.incrementXO. p incrementYO. store(getPairO).
// Использование критической секции-class PairManager2 extends PairManager { public void incrementO { Pair temp.
synchronized(this) {
p incrementXO; p. incrementYO; temp = getPairO,
}
store(temp).
class PairManipulator implements Runnable { private PairManager pm; public PairManipulator(PairManager pm) { this pm = pm,
}
public void run О { while(true)
pm. increment);
}
public String toStringO {
return "Pair: " + pm.getPairO +
" checkCounter = " + pm checkCounter.get О;
}
}
class PairChecker implements Runnable { private PairManager pm; public PairChecker(PairManager pm) {
}
public class CriticalSection { //
Сравнение двух подходов-static voidtestApproaches(PairManager pmanl. PairManager pman2) {
ExecutorService exec = Executors newCachedThreadPool. PairManipulator
pml = new PairManipulator(pmanl), pm2 = new PairManipulator(pman2), PairChecker
pcheckl = new PairChecker(pmanl), pcheck2 = new PairChecker(pman2), exec execute(pml), exec execute(pm2), exec execute(pcheckl); exec execute(pcheck2), try {
TimeUnit MILLISECONDS sleep(500); } catchdnterruptedException e) {
System out.printlnC'Sleep interrupted"),
}
System.out printin("pml " + pml + "\npm2: " + pm2). System exit(O),
}
public static void main(String[] args) { PairManager
pmanl = new PairManagerlO, pman2 = new PairManager2; testApproaches(pmanl. pman2);
}
} /* Output-
pml- Pair. x. 15, у 15 checkCounter = 272565 pm2- Pair- x. 16, y: 16 checkCounter = 3956974 */// ~
Как было отмечено, класс Pair не приспособлен к работе с потоками, поскольку его инвариант (предположительно произвольный) требует равенства обоих переменных. Вдобавок, как мы уже видели в этой главе, операции инкремента небезопасны в отношении к потокам, и, так как ни один из методов не был объявлен как synchronized, мы не можем считать, что объект Pair останется неповрежденным в многопоточной программе.
Представьте, что вы получили готовый класс Pair, который должен работать в многопоточных условиях. Класс PairManager хранит объекты Pair и управляет любым доступом к ним. Заметьте, что единственными открытыми (public) методами являются getPair, объявленный как synchronized, и абстрактный метод doTask. Синхронизация этого метода будет осуществлена при его реализации.
this pm = pm.
}
public void run {
while(true) {
pm checkCounter.i ncrementAndGet; pm getPa>r checkState,
Структура класса PairManager, в котором часть функциональности базового класса реализуется одним или несколькими абстрактными методами, определенными производными классами, называется на языке паттернов проектирования «шаблонным методом». Паттерны проектирования позволяют инкапсулировать изменения в коде — здесь изменяющаяся часть представлена методом increment. В классе PairManagerl метод increment полностью синхронизирован, в то время как в классе PairManager2 только часть его была синхронизирована посредством синхронизируемой блокировки. Обратите внимание еще раз, что ключевые слова synchronized не являются частью сигнатуры метода и могут быть добавлены во время переопределения.
Метод store добавляет объект Pair в синхронизированный контейнер Array-List, поэтому операция является потоково-безопасной. Следовательно, в защите он не нуждается, поэтому его вызов размещен за пределами синхронизируемого блока.
Класс PairManipulator создается для тестирования двух разновидностей Pair-Manager: метод increment вызывается в задаче в то время, как в другой задаче работает PairChecker. Метод main создает два объекта PairManipulator и дает им поработать в течение некоторого времени, после чего выводятся результаты по каждому PairManipulator.