Брюс Эккель - Философия Java3
queue.add(new PrioritizedTask(i)); // Предохранитель для остановки всех задач: queue.add(new PrioritizedTask EndSentinel(exec)); } catchdnterruptedException e) {
// Приемлемый вариант выхода
}
print("Завершение PrioritizedTaskProducer");
}
}
class PrioritizedTaskConsumer implements Runnable { private PriorityBlockingQueue<Runnable> q; public PrioritizedTaskConsumer(
PriorityBlockingQueue<Runnable> q) { this.q = q;
}
public void run() {
while('Thread interruptedO)
// Использование текущего потока для запуска задачи q.takeO run(). } catch(InterruptedException e) {
// Приемлемый вариант выхода
}
print("Завершение PrioritizedTaskConsumer").
}
}
public class PriorityBlockingQueueDemo {
public static void main(String[] args) throws Exception { Random rand = new Random(47);
ExecutorService exec = Executors newCachedThreadPoolО, PriorityBlockingQueue<Runnable> queue =
new PriorityBlockingQueue<Runnable>(); exec execute(new PrioritizedTaskProducer(queue. exec)), exec execute(new PrioritizedTaskConsumer(queue)),
}
} ///:-
Как и в предыдущем примере, последовательность создания объектов PrioritizedTask сохраняется в контейнере List sequence для сравнения с фактическим порядком выполнения. Метод run() делает небольшую паузу, а затем выводит информацию об объекте, а предохранитель EndSentinel выполняет те же функции, что и прежде.
PrioritizedTaskProducer и PrioritizedTaskConsumer связываются друг с другом через PriorityBlockingQueue. Так как сам блокирующий характер очереди обеспечивает всю необходимую синхронизацию, явная синхронизация не нужна — при чтении вам не нужно думать о том, содержит ли очередь элементы, потому что при отсутствии элементов очередь просто заблокирует читающую сторону.
Управление оранжереей на базе ScheduledExecutor
В главе 10 была представлена система управления гипотетической оранжереей, которая включала (отключала) различные устройства и регулировала их работу. Происходящее можно преобразовать в контекст многозадачности: каждое событие оранжереи представляет собой задачу, запускаемую в заранее заданное время. Класс ScheduledThreadPoolExecutor предоставляет именно тот сервис, который необходим для решения задачи. Используя методы schedule() (однократный запуск задачи) или scheduleAtFixedRate() (повторение задачи с постоянным промежутком), мы создаем объекты Runnable, которые должны запуститься в положенное время. Сравните это решение с тем, что приведено в главе 10, и посмотрите, насколько оно упрощается благодаря готовой функциональности ScheduledThreadPoolExecutor:
//: concurrency/GreenhouseScheduler.java
// Новая реализация innerclasses/GreenhouseController.java
// с использованием ScheduledThreadPoolExecutor.
// {Args- 5000}
import java.util.concurrent.*,
import java.util *;
public class GreenhouseScheduler {
private volatile boolean light = false, private volatile boolean water = false; private String thermostat = "Day", public synchronized String getThermostatO { return thermostat,
}
public synchronized void setThermostat(String value) { thermostat = value;
}
ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(10), public void schedule(Runnable event, long delay) {
scheduler schedule(event,delay.TimeUnit MILLISECONDS);
}
public void
repeat(Runnable event, long i niti alDel ay, long period) { scheduler scheduleAtFixedRate(
event, i niti alDel ay. period, TimeUnit MILLISECONDS);
}
class LightOn implements Runnable { public void run() {
// Сюда помещается аппаратный вызов, выполняющий // физическое включение света System out.printin("Свет включен"); light = true;
}
}
class LightOff implements Runnable { public void run() {
// Сюда помещается аппаратный вызов, выполняющий // физическое выключение света. System.out.printin("Свет выключен"), light = false,
}
}
class WaterOn implements Runnable { public void run() {
// Здесь размещается код включения // системы полива.
System out printlnC"Полив включен"); water = true;
}
}
class WaterOff implements Runnable { public void run О {
// Здесь размещается код выключения // системы полива
System out.printin("Полив выключен"); water = false,
}
}
class ThermostatNight implements Runnable { public void run() {
// Здесь размещается код управления оборудованием System.out.ргШ1п("Включение ночного режима"); setThermostat("Ho4b"), } продолжение &
}
class ThermostatDay implements Runnable { public void run() {
// Здесь размещается код управления оборудованием System out рпп^пСВключение дневного режима"), setThermostatCfleHb"),
}
}
class Bell implements Runnable {
public void run() { System.out.println("Вам!"), }
}
class Terminate implements Runnable { public void run() {
System out ргШ1п("Завершение"); scheduler shutdownNowO;
// Для выполнения этой операции необходимо запустить // отдельную задачу, так как планировщик был отключен-new ThreadО {
public void runО {
for(DataPoint d • data)
System.out.println(d);
}
}. startO;
}
}
// Новая возможность: коллекция данных static class DataPoint {
final Calendar time; final float temperature; final float humidity;
public DataPoint(Calendar d, float temp, float hum) { time = d;
temperature = temp; humidity = hum;
}
public String toStringO {
return time getTimeO + String.formate
" температура: %l$.lf влажность: *2$.2f". temperature, humidity);
}
}
private Calendar lastTime = Calendar.getlnstanceO; { // Регулировка даты до получаса
1astTime.set(Calendar.MINUTE, 30); lastTime set(Calendar.SECOND. 00);
}
private float lastTemp = 65.Of.
private int tempDirecti on = +1;
private float lastHumidity = 50 Of;
private int humidityDirecti on = +1;
private Random rand = new Random(47);
List<DataPoint> data = Collections.synchronizedList(
new ArrayList<DataPoint>0); class CollectData implements Runnable { public void run О {
System.out.println("Сбор данных"); synchronized(GreenhouseScheduler.this) {
1astTi me.set(Ca1enda r. MINUTE.
lastTime.get(Calendar.MINUTE) + 30); // С вероятностью 1/5 происходит смена направления: if(rand.nextlnt(5) == 4)
tempDirecti on = -tempDirecti on; // Сохранить предыдущее значение: lastTemp = lastTemp +
tempDi recti on * (1 Of + rand.nextFloatO); if(rand.nextlnt(5) == 4)
humidityDirecti on = -humidityDirecti on; lastHumidity = lastHumidity +
humidityDi recti on * rand.nextFloatO; // Объект Calendar необходимо клонировать, иначе // все DataPoint будут содержать ссылки // на одно и то же lastTime. // Для базового объекта - такого, как Calendar -// вызова cloneO вполне достаточно, data.add(new DataPoint((Calendar)1astTime.cloneO. lastTemp. lastHumidity));
}
}
}
public static void main(String[] args) {
GreenhouseScheduler gh = new GreenhouseSchedulerO;
gh.schedule(gh.new TerminateO. 5000);
// Former "Restart" class not necessary:
gh.repeat(gh.new BellO. 0. 1000);
gh.repeat(gh.new ThermostatNightО. 0. 2000);
gh. repeat (gh. new LightOnO, 0. 200);
gh. repeat (gh. new LightOffO. 0. 400);
gh.repeat(gh.new WaterOn0. 0, 600);
gh. repeat (gh. new WaterOffO. 0. 800);
gh.repeat(gh.new ThermostatDayO. 0. 1400);
gh.repeat(gh.new CollectDataO. 500. 500).
}
} ///:-
В этой версии, помимо реорганизации кода, добавляется новая возможность: сбор данных о температуре и влажности в оранжерее. Объект DataPoint содержит и выводит одну точку данных, а запланированная задача CollectData генерирует данные имитации и включает их в List<DataPoint> при каждом запуске.
Обратите внимание на ключевые слова volatile и synchronized; благодаря им задачи не мешают работе друг друга. Все методы контейнера List с элементами DataPoint синхронизируются с использованием метода synchronizedList() библиотеки java. u til. Со lie cti о n s при создании List.
Семафоры
При обычной блокировке доступ к ресурсу в любой момент времени разрешается только одной задаче. Семафор со счетчиком позволяет п задачам одновременно* обращаться к ресурсу. Можно считать, что семафор «выдает разрешения» на использование ресурса, хотя никаких реальных объектов разрешений в этой схеме нет.
В качестве примера рассмотрим концепцию пула объектов: объекты, входящие в пул, «выдаются» для использования, а затем снова возвращаются в пул после того, как пользователь закончит работу с ними. Эта функциональность инкапсулируется в параметризованном классе:
// concurrency/Pool java
// Использование Semaphore в Pool ограничивает количество // задач, которые могут использовать ресурс import java util concurrent *. import java util *,
public class Pool<T> { private int size,
private List<T> items = new ArrayList<T>(); private volatile boolean[] checkedOut, private Semaphore available, public Pool(Class<T> classObject, int size) { this.size = size, checkedOut = new boolean[size], available = new Semaphore(size, true); // Заполнение пула объектами for(int i =0, i < size, ++i) try {
// Предполагается наличие конструктора по умолчанию items add(classObject newInstanceO). } catch(Exception e) {
throw new RuntimeException(e);
}
}
public T checkout О throws InterruptedException { available acquireO; return getltemO,
}
public void checkIn(T x) { if(releaseltem(x))
available releasee);
}
private synchronized T getltemO {
for(int i =0; i < size, ++i) if(!checkedOut[i]) {
checkedOut[i] = true, return items get(i);
}
return null. // Семафор предотвращает переход в зту точку
}
private synchronized boolean releaseItem(T item) { int index = items indexOf(item). if(index == -1) return false; // Отсутствует в списке if(checkedOut[index]) {
checkedOut[index] = false, return true,
}
return false; // He был освобожден
}
В этой упрощенной форме конструктор использует newlnstance() для заполнения пула объектами. Если вам понадобится новый объект, вызовите check-Out(); завершив работу с объектом, передайте его checkln().
Логический массив checkedOut отслеживает выданные объекты. Для управления его содержимым используются методы getltem() и releaseltem(). В свою очередь, эти методы защищены семафором available, поэтому в checkOut() семафор available блокирует дальнейшее выполнение при отсутствии семафорных разрешений (то есть при отсутствии объектов в пуле). Метод checkln() проверяет действительность возвращаемого объекта, и, если объект действителен, разрешение возвращается семафору.
Для примера мы воспользуемся классом Fat. Создание объектов этого класса является высокозатратной операцией, а на выполнение конструктора уходит много времени:
//: concurrency/Fat java
// Объекты, создание которых занимает много времени
public class Fat {
private volatile double d. // Предотвращает оптимизацию private static int counter = 0. private final int id = counter++. public FatО {
// Затратная, прервываемая операция for(int i = 1: i < 10000; i++) {
d += (Math PI + Math.E) / (double)i.
}
}
public void operationO { System out println(this); } public String toStringO { return "Fat id: " + id; } } ///:-