在一个视频网上找到了喜欢的在线视频资源,没有下载按钮,只能自己下载了,看了 一下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,线程都是正常的,我就猜想 是不是哪里 出现问题,搜了 一下,参考了这篇文章:
连接
按照前辈所说,不适合设置异常处理类来处理 ,而且我这里只是 个人小玩具 ,有三个办法:
- 异常处理直接打印到控制台,但是如果多个线程同时打印 ,输出可能是乱 的
- 打印到日志,我比较懒,不想再拉配置文件和添加依赖,pass
- 对于结果进行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>