Философия Java3
Шрифт:
Когда метод main создает объекты-потоки Thread, он не сохраняет на них ссылки. Обычный объект, «забытый» таким образом, стал бы легкой добычей сборщика мусора, но только не объект-поток Thread. Каждый поток (Thread) самостоятельно «регистрирует» себя, то есть на самом деле ссылка на него где-то существует, и сборщик мусора не вправе удалить его объект.
Исполнители
Исполнители (executors), появившиеся в библиотеке java.util.concurrent в Java SE5, упрощают многозадачное программирование за счет автоматизации управления объектами Thread. Они создают
Вместо явного создания объектов 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
Исполнитель 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 позволит быстро организовать координацию доступа при построении рабочего прототипа.