Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 112 113 114 115 116 117 118 119 120 ... 132

При разных запусках программы будут получены разные результаты, поскольку работа планировщика потоков недетерминирована. Более того, вы наверняка увидите значительные различия в результатах работы данной программы-примера, запуская ее на различных версиях пакета JDK. К примеру, предыдущие версии JVM не слишком часто выполняли квантование времени, соответственно, поток 1 мог первым закончить свой цикл, затем все свои итерации произвел бы поток 2, и т. д. Фактически то же самое получилось бы, если бы вызывалась процедура, выполняющая все циклы одновременно, за тем исключением, что запуск совокупности потоков требует больших издержек. Более поздние версии JDK обеспечивают более качественное квантование, и каждый поток регулярно получает свою долю внимания. Как правило, Sun не упоминает о подобных изменениях, так что рассчитывать на определенные «правила поведения» потоков не стоит. Лучше всего при написании кода с потоками занять максимально консервативную позицию.

Когда метод main() создает объекты-потоки Thread, он не сохраняет на них ссылки. Обычный объект, «забытый» таким образом, стал бы легкой добычей сборщика мусора, но только не объект-поток Thread. Каждый поток (Thread) самостоятельно «регистрирует» себя, то есть на самом деле ссылка на него где-то существует, и сборщик мусора не вправе удалить его объект.

Исполнители

Исполнители (executors), появившиеся в библиотеке java.util.concurrent в Java SE5, упрощают многозадачное программирование за счет автоматизации управления объектами Thread. Они создают дополнительную логическую прослойку между клиентом и выполнением задачи; задача выполняется не напрямую клиентом, а промежуточным объектом. Исполнители позволяют управлять выполнением асинхронных задач без явного управления жизненным циклом потоков. Именно такой способ запуска задач рекомендуется использовать в Java SE5/6.

Вместо явного создания объектов Thread в MoreBasicThreads.java мы можем воспользоваться исполнителем. Объект LiftOff умеет выполнять определенную операцию и предоставляет единственный метод для выполнения. Объект Execu-torService умеет создавать необходимый контекст для выполнения объектов Runnable. В следующем примере класс CachedThreadPool создает один поток для каждой задачи. Обратите внимание: объект ExecutorService создается статическим методом класса Executors, определяющим разновидность исполнителя:

// concurrency/CachedThreadPool java

import java util concurrent *.

public class CachedThreadPool {

public static void main(String[] args) {

ExecutorService exec = Executors.newCachedThreadPoolО, for(int i = 0, i < 5; i++)

exec execute(new LiftOffO); exec shutdownО.

}

} /* Output (Пример)

#0(9). #0(8). #1(9). #2(9). #3(9). #4(9). #0(7). #1(8). #2(8). #3(8). #4(8). #0(6).

#1(7). #2(7). #3(7). #4(7). #0(5). #1(6). #2(6). #3(6). #4(6), #0(4). #1(5). #2(5).

#3(5). #4(5). #0(3). #1(4). #2(4). #3(4). #4(4). #0(2). #1(3). #2(3). #3(3). #4(3).

#0(1). #1(2). #2(2). #3(2). #4(2). #0(Liftoff!). #1(1). #2(1). #3(1). #4(1).

#1(Liftoff!). #2(Liftoff1). #3(Liftoff!). #4(Liftoff!).

*/// ~

Очень часто для создания и управления всеми задачами в системе достаточно одного исполнителя.

Вызов shutdown() предотвращает передачу Executor новых задач. Текущий поток (в данном случае тот, в котором выполняется main()) продолжает выполняться со всеми задачами, переданными до вызова shutdown(). Работа программы прекращается после завершения всех задач в Executor.

CachedThreadPool в этом примере легко заменяется другим типом Executor. Например, в потоковом пуле фиксированного размера (FixedThreadPool) используется ограниченный набор потоков для выполнения переданных задач:

// concurrency/FixedThreadPool.java

import java.util.concurrent *.

public class FixedThreadPool {

public static void main(String[] args) {

// В аргументе конструктора передается количество потоков ExecutorService exec = Executors newFixedThreadPool(5);

for(int i = 0; i < 5; i++)

exec, execute (new LiftOffO); exec.shutdownO;

}

} /* Output: (Sample)

#0(9). #0(8). #1(9). #2(9). #3(9). #4(9). #0(7). #1(8). #2(8). #3(8). #4(8). #0(6).

#1(7). #2(7). #3(7). #4(7). #0(5). #1(6). #2(6). #3(6). #4(6). #0(4). #1(5). #2(5).

#3(5). #4(5). #0(3). #1(4). #2(4). #3(4). #4(4). #0(2). #1(3). #2(3). #3(3). #4(3).

#0(1). #1(2). #2(2). #3(2). #4(2). #0(Liftoff!), #1(1). #2(1). #3(1). #4(1).

#1(Liftoff!). #2(Liftoff!). #3(Liftoff!). #4(Liftoff!).

*///:-

С FixedThreadPool дорогостоящая операция создания потоков выполняется только один раз, в самом начале, поэтому количество потоков остается фиксированным. Это способствует экономии времени, поскольку вам не приходится нести затраты, связанные с созданием потока, для каждой отдельной задачи. В системах, управляемых событиями, обеспечивается максимальная скорость выполнения обработчиков событий, так как они могут просто получить поток из пула. Перерасход ресурсов в такой схеме исключен, так как FixedThreadPool использует ограниченное количество объектов Thread.

Исполнитель SingleThreadExecutor представляет собой аналог FixedThreadPool с одним потоком. Например, он может быть полезен для выполнения долгосрочных операций (скажем, прослушивания входящих подключений по сокету) и для коротких операций, выполняемых в отдельных потоках, — скажем, обновления локальных или удаленных журналов.

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

//: concurrency/SingleThreadExecutor.java

import java.util.concurrent.*;

public class SingleThreadExecutor {

public static void main(String[] args) { ExecutorService exec =

Executors.newSi ngleThreadExecutor(); for(int i = 0; i < 5; i++)

exec, execute (new LiftOffO); exec.shutdownO;

}

} /* Output:

#0(9). #0(8). #0(7). #0(6). #0(5). #0(4). #0(3). #0(2). #0(1). #0(L1ftoff!). #1(9).

#1(8). #1(7). #1(6). #1(5). #1(4). #1(3). #1(2). #1(1). #1(Liftoff!). #2(9). #2(8).

#2(7). #2(6). #2(5). #2(4). #2(3). #2(2). #2(1). #2<Liftoff!). #3(9). #3(8). #3(7).

#3(6), #3(5). #3(4). #3(3). #3(2). #3(1). «(Liftoff!). #4(9). #4(8). #4(7). #4(6).

#4(5). #4(4). #4(3). #4(2). #4(1). #4(Liftoff!).

Другой пример: допустим, имеется группа потоков, выполняющих операции с использованием файловой системы. Вы можете запустить эти задачи под управлением SingleThreadExecutor, чтобы в любой момент гарантированно выполнялось не более одной задачи. При таком подходе вам не придется возиться с синхронизацией доступа к общим ресурсам (без риска для целостности файловой системы). Возможно, в окончательной версии кода будет правильнее синхронизировать доступ к ресурсу (см. далее в этой главе), но SingleThreadExecutor позволит быстро организовать координацию доступа при построении рабочего прототипа.

Возврат значений из задач

Интерфейс Runnable представляет отдельную задачу, которая выполняет некоторую работу, но не возвращает значения. Если вы хотите, чтобы задача возвращала значение, реализуйте интерфейс Callable вместо интерфейса Runnable. Параметризованный интерфейс Callable, появившийся в Java SE5, имеет параметр типа, представляющий возвращаемое значение метода call() (вместо run()), а для его вызова должен использоваться метод ExecutorService submit(). Простой пример:

//. concurrency/Cal1ableDemo.java

import java.util concurrent.*;

import java.util.*,

class TaskWithResult implements Callable<String> {

private int id;

public TaskWithResult(int id) { this id = id.

}

public String call О {

return "результат TaskWithResult " + id;

}

}

public class CallableDemo {

public static void main(String[] args) {

ExecutorService exec = Executors.newCachedThreadPoolО; ArrayList<Future<String>> results =

new ArrayList<Future<String»(). for(int i = 0; i < 10; i++)

results.add(exec submit(new TaskWithResult(i))); for(Future<String> fs ; results) try {

// Вызов get О блокируется до завершения; System.out.pri nt1n(fs.get()); } catch(InterruptedException e) { System.out.println(e). return;

} catch(ExecutionException e) { System out println(e); } finally {

exec.shutdown();

}

} /* Output•

результат TaskWithResult О результат TaskWithResult 1 результат TaskWithResult 2 результат TaskWithResult 3 результат TaskWithResult 4 результат TaskWithResult 5 результат TaskWithResult 6 результат TaskWithResult 7 результат TaskWithResult 8 результат TaskWithResult 9 *///:-

Метод submit() создает объект Future, параметризованный по типу результата, возвращаемому Callable. Вы можете обратиться к Future с запросом isDone(), чтобы узнать, завершена ли операция. После завершения задачи и появления результата производится его выборка методом get(). Если get() вызывается без предварительной проверки isDone(), вызов блокируется до появления результата. Также можно вызвать get() с интервалом тайм-аута.

Перегруженный метод Executors.callable() получает Runnable и выдает Callable. ExecutorService содержит методы для выполнения коллекций объектов Callable.

Ожидание

Другим способом управления вашими потоками является вызов метода sleep(), который переводит поток в состояние ожидания на заданное количество миллисекунд. Если в классе LiftOff заменить вызов yield() на вызов метода sleep(), будет получен следующий результат:

//: concurrency/SleepingTask.java // Вызов sleepO для приостановки потока, import java.util.concurrent.*;

public class SIeepingTask extends LiftOff { public void run() { try {

while(countDown-- > 0) {

System.out.pri nt(status());

// Старый стиль.

// Thread.sleep(lOO);

// Стиль Java SE5/6:

TimeUnit MILLISECONDS.sieep(100);

}

} catchdnterruptedException e) {

System.err.pri ntin("Interrupted");

}

}

public static void main(String[] args) {

ExecutorService exec = Executors.newCachedThreadPoolО; for(int i = 0; i < 5; i++)

exec.execute(new SIeepi ngTask()); exec.shutdownO;

}

#0(9). #1(9)

#2(7). #3(7)

1 ... 112 113 114 115 116 117 118 119 120 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий