Философия Java3
Шрифт:
while(workingTellers.size > 1) reassignOneTellerO;
}
// Поручаем кассиру другую работу или отправляем его отдыхать: private void reassignOneTellerO {
Teller teller = workingTellers.pollО;
tel 1 er. doSomethi ngEl seO,
tel1ersDoi ngOtherThi ngs.offer(tel1er);
}
public void runO { try {
while(!Thread.interruptedO) {
TimeUnit.MILLISECONDS.sleep(adjustmentPeriod);
adjustTellerNumberO;
System.out.print(customers +"{");
for(Teller teller workingTellers)
System.out.print(teller.shortString + " "); System.out.printIn("}"); } продолжение &
} catchdnterruptedException
System.out.printin(this + "прерван");
}
System.out println(this + "завершается");
}
public String toStringO { return "TellerManager "; }
}
public class BankTellerSimulation {
static final int MAX_LINE_SIZE = 50;
static final int ADJUSTMENT_PERIOD = 1000;
public static void main(String[] args) throws exception {
ExecutorService exec = Executors.newCachedThreadPoolО; // Если очередь слишком длинна, клиенты уходят: CustomerLine customers =
new CustomerLi ne(MAX_LINE_SIZE); exec.execute(new CustomerGenerator(customers)); // TellerManager добавляет и убирает кассиров // по мере необходимости: exec.execute(new TellerManager(
exec, customers. ADJUSTMENT_PERIOD)); if(args.length > 0) // Необязательный аргумент
Ti meUni t.SECONDS.s1eep(new Integer(args[0])).
else {
System.out.printIn("Press 'Enter' to quit"); System.in.readО;
}
exec.shutdownNowO;
}
} /* Output:
[429][200][207] { K0 K1 } [861][258][140][322] { K0 K1 } [575][342][804][826][896][984] { КО K1 K2 } [984][810][141][12][689][992][976][368][395][354] { КО K1 K2 КЗ } Teller 2 прерван Teller 2 завершается Teller 1 прерван Teller 1 завершается TellerManager прерван TellerManager завершается Teller 3 прерван Teller 3 завершаетсяч Teller 0 прерван Teller 0 завершается CustomerGenerator прерван CustomerGenerator завершается *///:-
Объекты Customer очень просты; они содержат только поле данных final int. Так как эти объекты никогда не изменяют своего состояния, они являются объектами, доступными только для чтения, и поэтому требуют синхронизации или использования volatile. Вдобавок каждая задача Teller удаляет из очереди ввода только один объект Customer и работает с ним до завершения, поэтому задачи все равно будут работать с Customer последовательно.
Класс CustomerLine представляет собой общую очередь, в которой клиенты ожидают обслуживания. Он реализован в виде очереди ArrayBlockingQueue с методом toString, который выводит результаты в желаемом формате.
Генератор CustomerGenerator присоединяется к CustomerLine и ставит объекты Customer в очередь со случайными интервалами.
Teller извлекает клиентов Customer из CustomerLine и обрабатывает их последовательно, подсчитывая количество клиентов, обслуженных за текущую смену. Если клиентов не хватает, его можно перевести на другую работу (doSome-thingElse), а при появлении большого количества клиентов — снова вернуть на обслуживание очереди методом serveCustomerLine. Чтобы приказать следующему кассиру вернуться к очереди, метод compareTo проверяет количество обслуженных клиентов,
чтобы приоритетная очередь автоматически ставила в начало кассира, работавшего меньше других.Вся основная деятельность выполняется в TellerManager. Этот класс следит за всеми кассирами и за тем, что происходит с клиентами. Одна из интересных особенностей данной имитации заключается в том, что она пытается подобрать оптимальное количество кассиров для заданного потока покупателей. Пример встречается в методе adjustTellerNumber — управляющей системе для надежной, стабильной регулировки количества кассиров. У всех управляющих систем в той или иной мере присутствуют проблемы со стабильностью; слишком быстрая реакция на изменения снижает стабильность, а слишком медленная переводит систему в одно из крайних состояний.
Резюме
В этой главе я постарался изложить основы многопоточного программирования с использованием потоков Java. Прочитав ее, читатель должен понять следующее:
1. Программу можно разделить на несколько независимых задач.
2. Необходимо заранее предусмотреть всевозможные проблемы, возникающие при завершении задач.
3. Задачи, работающие с общими ресурсами, могут мешать друг другу. Основным средством предотвращения конфликтов является блокировка.
4. В неаккуратно спроектированных многозадачных системах возможны взаимные блокировки.
Очень важно понимать, когда рационально использовать параллельное выполнение, а когда этого делать не стоит. Основные причины для его использования:
• управление несколькими подзадачами, одновременное выполнение которых позволяет эффективнее распоряжаться ресурсами компьютера (включая возможность незаметного распределения этих задач по нескольким процессорам);
• улучшенная организация кода;
• удобство для пользователя.
Классический пример распределения ресурсов — использование процессора во время ожидания завершения операций ввода/вывода. Классический пример чуткого пользовательского интерфейса — отслеживание нажатий кнопки «Прервать» во время продолжительного процесса загрузки.
Дополнительным преимуществом потоков является то, что они заменяют «тяжелое» переключение контекста процессов (порядка 1 ООО и более инструкций) «легким» переключением контекста выполнения (около 100 инструкций). Так как все потоки процесса разделяют одно и то же пространство памяти, легкое переключение затрагивает только выполнение программы и локальные переменные. С другой стороны, чередование процессов — тяжелое переключение контекста — требует обновления всего пространства памяти.
Основные недостатки многозадачности:
1. Замедление программы, связанное с ожиданием освобождения блокированных ресурсов.
2. Дополнительная нагрузка на процессор для управления потоками.
3. Совершенно ненужная сложность, являющаяся следствием неудачных решений при проектировании программы.
4. Аномальные ситуации: взаимные блокировки, конфликты доступа, гонки и т. д.
5. Непоследовательное поведение на различных платформах. Например, при разработке некоторых примеров для данной книги я обнаружил ситуации гонки, быстро проявлявшиеся на некоторых компьютерах, но незаметные на других.