1. 程式人生 > >問題記錄:執行緒池批量下載檔案

問題記錄:執行緒池批量下載檔案

在一個視訊網上找到了喜歡的線上視訊資源,沒有下載按鈕,只能自己下載了,看了 一下network,是ts分片的檔案,好在是命名挺規範的,都是 xxxx + index + .ts的格式,方便了 我下載。
一開始我用單執行緒下載,下了半天只下了200個 分片,總共有800個,因此想到了執行緒池,一直以為我是會用執行緒池的,結果才發現了自己的不足,記錄一下。

構造器

        /*和資料庫連線池很像*/
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, // 小於corePoolSize時即使其他執行緒空閒也會建立
                5, // 如果當前執行緒都忙,但是小於匯流排程,會建立新的
                1000, // 空閒(非核心)執行緒存活時間
                TimeUnit.MINUTES,
                new ArrayBlockingQueue<>(5)); // 如果使用synchronousQueue 執行緒最多隻能達到corePoolSize


舉個例子,我使用了
ExecutorService service = Executors.newFixedThreadPool(4);
內部就是:
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>()); // 任務佇列是無界的,因此不需要在意拒絕策略。
    }
說到 拒絕 策略,一共有4種
 /**
         * maximumPoolSize有限,等待任務佇列使用的是有界佇列,倘若是linkedBlockQueue總是排隊
         * 預設AbortPolicy 丟擲異常
         * Discard 忽略新任務, 不報錯
         * DiscardOld 取消舊任務
         * CallerRunsPolicy 在提交執行緒中執行任務
         */

下面進入正題

異常

我的程式借用了httpclient

HttpClient httpClient= new DefaultHttpClient();
ExecutorService service = Executors.newFixedThreadPool(4);
        Collection<Callable<Integer>> futureTasks = new LinkedList<>();
        for (int i = 1; i <= totalPierce; i++) {
            final int v = i;
            futureTasks.add(() -> {
                download(httpClient, v);
                return v;
            });
        }
        List<Future<Integer>> futureList = service.invokeAll(futureTasks);
private static void download(HttpClient client, int i) {
        System.out.println("連線" + i + "ts");
        HttpGet httpget = new HttpGet(makeURL(i));
        try {
            HttpResponse response = client.execute(httpget);
            HttpEntity entity = response.getEntity();
            String file = "F:\\seg-" + i + ".ts";
            FileOutputStream os = new FileOutputStream(file);
            entity.writeTo(os);
            os.close();
            System.out.println("完成" + i);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

執行結果也特別奇怪,先輸出1,2,3,4,阻塞 一段時間,然後 一股腦的輸出連線直到 totalPierce.但是沒有顯示完成的,開了VisualVM,執行緒都是正常的,我就猜想 是不是哪裡 出現問題,搜了 一下,參考了這篇文章:
連線
按照前輩所說,不適合設定異常處理類來處理 ,而且我這裡只是 個人小玩具 ,有三個辦法:

  1. 異常處理直接列印到控制檯,但是如果多個執行緒同時列印 ,輸出可能是亂 的
  2. 列印到日誌,我比較懶,不想再拉配置檔案和新增依賴,pass
  3. 對於結果進行get,在get處進行異常處理

原來如此,如果futureTask異常,future.get時是會 丟擲的!
新增如下程式碼 :

for (Future f :
                futureList) {
            try {
                f.get();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

        }

這才發現 defaultHttpclient不是執行緒安全的!
因此改用

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(100);

        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(cm)
                .build();

暫時沒有問題,但是還沒有完全好,我仍然少考慮了很多東西,比如異常的分片需要 重試?下載速度仍然沒有達到很高,追憶前輩們proxyee-down IDM等等的下載工具,真是高山仰止,還需努力

附上httpClient依賴:

		<dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>