Чтение онлайн

ЖАНРЫ

Философия 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 void

testApproaches(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.

Поделиться с друзьями: