Markdown編輯器上線啦,終於等到你了!使用這個編輯器寫了兩篇,感覺還是不錯的!不過還是有一些問題,慢慢熟悉吧!
執行器
構建一個新的線程是有一定的代價的,因爲涉及到和操作系統的交互。如果程序中創建了大量的生命週期很短的線程,應該使用線程池(thread pool)。
另一個使用線程池的理由是減少併發線程的數目。線程數量太多會大大降低性能甚至會使虛擬機崩潰。如果有一個會創建許多線程的算法,應該使用一個線程數“固定的”線程池以限制併發線程的總數。
Executor類構建線程池的靜態方法
方法 | 描述 |
---|---|
newCachedThreadPool | 必要時創建新線程,空閒線程會被保留60秒 |
newFixedThreadPool | 該池包含固定數量的線程;空閒線程會一直被保留 |
newSingleThreadExecutor | 只有一個線程的“池”,該線程順序執行每一個提交的任務 |
newScheduledThreadPool | 用於預定執行而構建的固定線程池,替代java.util.Timer |
newSingleThreadScheduledExecutor | 用於預定執行而構建的單線程“池” |
線程池
newCachedThreadPool方法構建了一個線程池,對於每個任務,如果有空閒的線程可用,立即讓它執行任務,如果沒有可用的空閒線程,則創建一個新線程。
newFixedThreadPool方法構建一個具有固定大小的線程池。如果提交的任務數多於空閒的線程數,那麼把得不到的服務的任務放置到隊列中。當其他任務完成以後再運行它們。
newSingleThreadExecutor是一個退化了的大小爲1的線程池:由一個線程執行提交的任務,一個接着一個。這三個方法返回實現了ExecutorService接口的ThreadPoolExecutor類的對象。
使用連接池:
- 調用Executors類中靜態的方法newCachedThreadPool或newFixedThreadPool。
- 調用submit提交Runnable或Callable對象。
- 如果想要取消一個任務,或如果提交Callable對象,那就要保存好返回的Future對象。
- 當不在提交任何任務時,調用shutdown。
MacthCounter類:
/**
* @author xzzhao
*/
public class MacthCounter implements Callable<Integer> {
private final File directory;
private final String keyword;
private final ExecutorService pool;
private int count;
public MacthCounter(File directory, String keyword, ExecutorService pool) {
super();
this.directory = directory;
this.keyword = keyword;
this.pool = pool;
}
@Override
public Integer call() throws Exception {
count = 0;
File[] files = directory.listFiles();
List<Future<Integer>> results = new ArrayList<>();
for (File file : files) {
if (file.isDirectory()) {
MacthCounter counter = new MacthCounter(file, keyword, pool);
FutureTask<Integer> task = new FutureTask<>(counter);
results.add(task);
} else {
if (search(file)) {
count++;
}
}
}
for (Future<Integer> result : results) {
count += result.get();
}
return count;
}
/**
* 搜索方法
*
* @param file
* @return 是否找到
*/
public boolean search(File file) {
try {
try (Scanner in = new Scanner(file)) {
boolean found = false;
while (!found && in.hasNextLine()) {
String line = in.nextLine();
if (line.contains(keyword)) {
found = true;
}
}
return found;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
ThreadPoolTest類:
/**
* @author xzzhao
*/
public class ThreadPoolTest {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("請輸入根目錄 :");
String directory = in.nextLine();
System.out.println("請輸入關鍵字 :");
String keyword = in.nextLine();
ExecutorService pool = Executors.newCachedThreadPool();
MacthCounter counter = new MacthCounter(new File(directory), keyword, pool);
Future<Integer> result = pool.submit(counter);
try {
System.out.println("匹配到的文檔數:" + result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
pool.shutdown();
int largestPoolSize = ((ThreadPoolExecutor) pool).getLargestPoolSize();
System.out.println("largestPoolSize=" + largestPoolSize);
}
}
預定執行
ScheduledExecutorService接口具有爲預定執行或重複執行任務而設計的方法。它是一種允許使用線程機制的Java.util.Timer的泛化。Executors類的newScheduledThreadPool和newSingleThreadScheduledExecutor方法將返回實現了ScheduledExecutorService接口的對象。
可以預定Runnable或Callable在初始的延遲之後只運行一次。也可以預定一個Runnable對象週期性地運行。
控制任務組
我們已經知道如何將一個執行器服務作爲線程池使用,以提高執行任務的效率。有時候,我們要使用執行器來做更有實際意義的事,控制一組相關的任務。例如,可以在執行器中使用shutdownNow方法取消所有的任務。
invokeAll方法提交所有對象到一個Callable對象的集合中,並返回一個Future對象的列表,代表所有任務的結果。這個方法的缺點是如果第一個任務花去了很多時間,那麼就可能不得不進行等待。將結果按可獲得的順序保存起來更有意義。可以用ExecutorComeletionService來進行排序。
具體可以查詢API。用常規的方法獲得一個執行器。然後,構建一個ExecutorComeletionService,提交任務給完成服務。該服務管理Future對象的阻塞隊列,其中包含已經提交的任務的執行結果。
大概如下:
ExecutorService executor = Executors.newCachedThreadPool();
ExecutorCompletionService service = new ExecutorCompletionService<>(executor);
for (Callable<T> task : tasks) {
service.submit(task);
}
for (int i = 0; i < task.size(); i++) {
processFurther(service.take().get());
}
Fork-Join框架
有的應用程序使用了大量的線程,但其中大多數都是空閒的。舉例來說,一個Web服務器可能會爲每個連接分別使用一個線程。另外一些應用可能對每個處理器內核分別使用一個線程,來完成計算密集的任務,如圖像或視頻處理。Java SE 7 中新引入了fork-join框架,專門用來支持後一類的應用。
我們來討論一個簡單的例子。假設我們想統計一個數組中有多少個元素滿足摸個特定的屬性。可以將這個數組一分爲二,分別對着兩部分進行統計,再將結果相加。
Counter 類:
/**
* @author xzzhao
*/
public class Counter extends RecursiveTask<Integer> {
public static final int THRESHOLD = 1000;
private final double[] values;
private final int from;
private final int to;
private final Filter filter;
public Counter(double[] values, int from, int to, Filter filter) {
super();
this.values = values;
this.from = from;
this.to = to;
this.filter = filter;
}
@Override
protected Integer compute() {
if (to - from < THRESHOLD) {
int count = 0;
for (int i = from; i < to; i++) {
if (filter.accept(values[i])) {
count++;
}
}
return count;
} else {
int mid = ((from + to) / 2);
Counter first = new Counter(values, from, mid, filter);
Counter second = new Counter(values, mid, to, filter);
invokeAll(first, second);
return first.join() + second.join();
}
}
}
Counter 類:
/**
* @author xzzhao
*/
public class ForkJoinTest {
public static void main(String[] args) {
final int SIZE = 10000000;
double[] numbers = new double[SIZE];
for (int i = 0; i < SIZE; i++) {
numbers[i] = Math.random();
}
Counter counter = new Counter(numbers, 0, numbers.length, new Filter() {
@Override
public boolean accept(double x) {
return x > 0.5;
}
});
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(counter);
System.out.println(counter.join());
}
}
採用框架可用的一種方式完成這種遞歸計算,需要提供一個擴展RecursiveTask的類,如果計算會生成一個結果的話,或者如果不生成任何結果,就可以提供一個擴展RecursiveAction的類。再覆蓋compute方法來生成並調用子任務,然後合併結果。
invokeAll方法接收很多任務並阻塞,知道所有的這些任務都已經完成。join方法將生成結果。對每個子任務都應用了join,並返回總和。