文档章节

透过妹子看本质:爬虫小问题,并发大学问-2并发症要治,还要有Future

wphmoon
 wphmoon
发布于 06/12 18:13
字数 2570
阅读 47
收藏 2

     上一篇我们用jsoup解决了爬虫解析的问题,但却留下了下载图片很慢,效率低下的问题。根据日志的观察,可以看到图片都是一张一张的下,这种速度怎么能跟的上我阅遍天下美女的雄心,于是,多线程下图必须要上场了。

    springboot配置多线程其实很简单,首先是要配好线程池,这个需要一个config类来配置:

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@EnableAsync
@Configuration
class TaskPoolConfig {

	@Bean("taskExecutor")
	public Executor taskExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		//设置核心线程数
		executor.setCorePoolSize(10);
		//设置最大线程数
		executor.setMaxPoolSize(20);
		//线程池所使用的缓冲队列
		executor.setQueueCapacity(200);
		 // 设置线程活跃时间(秒)
		executor.setKeepAliveSeconds(60);
	    //  线程名称前缀
		executor.setThreadNamePrefix("taskExecutor-");
		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
		// 等待所有任务结束后再关闭线程池
		executor.setWaitForTasksToCompleteOnShutdown(true);
		// 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
		executor.setAwaitTerminationSeconds(60);
		return executor;
	}
}

    @Configuration注解表示这是一个配置类,效果等同于xml、properties、yml之类的配置文件,不过用类可以更灵活一下。@EnableAsync则说明这个工程需要用多线程,否则即使后面挂了异步的注解,也是不生效的。另外很多教程要求@EnableAsync挂在SpringBootApplication类上面,其实不是必须的,在配置类加了@EnableAsync,多线程就自动生效了。具体线程池的具体参数代码上都注释了,就不详述了。

    配置完成后,我们自然要对下载图片的controller下手了,马上配置下载图片的类为异步类,开始多线程下载。

downImages()方法是我工具类DownloadUtils的一个static 方法,我把@Async注解加上去,表示这是一个异步方法,会多线程执行。我然后在controller里面调用,想着电脑飞快的下载妹子图了。但一运行,我错了,图片仍然在一张一张按顺序缓慢的下载:

开始下载
2019-06-12 12:11:56.854  INFO 13404 --- [nio-8080-exec-1] com.skyblue.crawel.utils.DownloadUtils   : http://ac.meijiecao.net/ac/img/znb/meizitu/20190611_meizitu_07.jpg
2019-06-12 12:12:00.453  INFO 13404 --- [nio-8080-exec-1] com.skyblue.crawel.utils.DownloadUtils   : http://ac.meijiecao.net/ac/img/znb/meizitu/20190611_meizitu_08.jpg
2019-06-12 12:12:06.620  INFO 13404 --- [nio-8080-exec-1] com.skyblue.crawel.utils.DownloadUtils   : http://ac.meijiecao.net/ac/img/znb/meizitu/20190611_meizitu_01.jpg
2019-06-12 12:12:17.294  INFO 13404 --- [nio-8080-exec-1] com.skyblue.crawel.utils.DownloadUtils   : http://ac.meijiecao.net/ac/img/znb/meizitu/20190611_meizitu_02.jpg

日志中[nio-8080-exec-1]就是主线程的名称,显然多线程没起作用,难道是我的线程池没起作用,我有向上翻了翻项目启动的日志,赫然发现:

2019-06-12 12:11:46.222  INFO 13404 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'taskExecutor'

在项目启动完成前,线程池taskExecutor就已经初始化完成了,可见线程池是已经建好了,但却没用上。这是为啥呢?

并发症第一条:static 方法使用@Async注解无效

既然static 方法不能用,那么我搞个非静态方法调用调用static方法,然后我在这个非静态方法上面使用@Async不就好了,我真是聪明机智。

@Async
		private void downloadMeizitu(String url) {
			......
					DownloadUtils.downImages(filePath, imgSrc, map);
            ......
		}

我继续憧憬着能够实现快速下图,满含希望的又开始了爬虫行动。

一阵尴尬的沉默....

我又失败了,日志中仍然是主线程在慢悠悠的下载着图片。

并发症第二条:异步方法和调用异步方法不能在同一个类里面

好吧,既然这么多规矩,我只好另外建了一个service类来申明异步方法,然后让controller调用

@Component
public class DownloadAsyncService {
	......

	@Async
	public void downloadImage(String filePath, String imgUrl, Map<String, String> requestPropMap) {
		 DownloadUtils.downImages(filePath, imgUrl, requestPropMap);
	}
    ......
@RestController
@RequestMapping("/crawler")
public class CrawlerController {

......

					String imgSrc = element.attr("src");
					Map<String,String> map = new HashMap<String,String>();
					map.put("Referer", url);
					map.put("User-Agent", "Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1");
					downloadAsyncService.downloadImage(filePath, imgSrc,map);//多线程下图
					logger.info(imgSrc);

......

这次我怀着忐忑的心情开始了下载......

2019-06-12 14:33:12.838  INFO 7384 --- [taskExecutor-10] c.s.crawel.service.DownloadAsyncService  : http://ac.meijiecao.net/ac/img/znb/meizitu/20190611_meizitu_05.jpg
2019-06-12 14:33:12.838  INFO 7384 --- [taskExecutor-10] c.s.crawel.service.DownloadAsyncService  : E:/youtube/images/zhainanfuli/21483
2019-06-12 14:33:17.674  INFO 7384 --- [ taskExecutor-3] c.s.crawel.service.DownloadAsyncService  : http://ac.meijiecao.net/ac/img/znb/meizitu/20190611_meizitu_01.jpg
2019-06-12 14:33:17.675  INFO 7384 --- [ taskExecutor-3] c.s.crawel.service.DownloadAsyncService  : E:/youtube/images/zhainanfuli/21483
2019-06-12 14:33:18.892  INFO 7384 --- [ taskExecutor-2] c.s.crawel.service.DownloadAsyncService  : http://ac.meijiecao.net/ac/img/znb/meizitu/20190611_meizitu_08.jpg
2019-06-12 14:33:18.892  INFO 7384 --- [ taskExecutor-2] c.s.crawel.service.DownloadAsyncService  : E:/youtube/images/zhainanfuli/21483
2019-06-12 14:33:19.324  INFO 7384 --- [ taskExecutor-7] c.s.crawel.service.DownloadAsyncService  : http://ac.meijiecao.net/ac/img/znb/meizitu/20190611_meizitu_10.jpg
2019-06-12 14:33:19.324  INFO 7384 --- [ taskExecutor-7] c.s.crawel.service.DownloadAsyncService  : E:/youtube/images/zhainanfuli/21483
2019-06-12 14:33:21.263  INFO 7384 --- [ taskExecutor-9] c.s.crawel.service.DownloadAsyncService  : http://ac.meijiecao.net/ac/img/znb/meizitu/20190611_meizitu_04.jpg
2019-06-12 14:33:21.263  INFO 7384 --- [ taskExecutor-9] c.s.crawel.service.DownloadAsyncService  : E:/youtube/images/zhainanfuli/21483
2019-06-12 14:33:21.575  INFO 7384 --- [ taskExecutor-8] c.s.crawel.service.DownloadAsyncService  : http://ac.meijiecao.net/ac/img/znb/meizitu/20190611_meizitu_09.jpg

看这个日志就知道,[taskExecutor-x],10个线程已经撒开了手脚各自下图去了,至此,多线程下图的问题终于解决了。

但我不是一个容易满足的人否则干嘛要下这么多妹子图,这种只求线程跑,不跟踪线程结果的事不是我这种有始有终,负责人的人会干出来的,我为了证明我的人品看这种图的人有什么人品,我决定要跟踪下线程的结果,什么时候结束,也方便以后线程结束时知道线程的运行时间,后续事件触发啥的。于是,我又掏出了Future。

Future是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果的接口。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。说白了就是对多线程任务的监控和数据传输,具体作用有三点:

  1. 判断任务是否完成;
  2. 能够中断任务;
  3. 能够获取任务执行结果

中断任务我们用不上,我们需要判断图片啥时候完成,另外还需要知道图片下完的时间是多少,让我们继续撸代码:

@Async
	public Future<DownloadFile> downloadImage(DownloadFile downloadFile,String filePath, String imgUrl, Map<String, String> requestPropMap) {
		 logger.info("====="+filePath);
		 DownloadUtils.downImages(filePath, imgUrl, requestPropMap);
		 downloadFile.setEndDate(new Date());
		 return new AsyncResult<DownloadFile> (downloadFile);
	}

DownloadFile是我记录下载信息的,先不用管。首先我把下载图片的一步方法加上了返回值,Future类型,返回一个时间。然后在controller里面取出futrue的结果,把结束时间打印到日志里面:

Future<Date> future = downloadAsyncService.downloadImage(filePath, imgSrc,map);//多线程下图
					try {
						logger.info(DateUtils.dateTimeDetail(future.get()));
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (ExecutionException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

我一运行,结果多线程又不灵了,一夜回到解放前,下图又开始单线程执行了。这是为啥呢,我打开Future.get()的注释,发现写着:

Waits if necessary for the computation to complete, and thenretrieves its result.

原来取值的时候会等待计算结果,直到计算完成返回结果值,这样的话多线程就等成了单线程了。

并发症第三条:FUTURE.GET()会阻塞多线程的运行,直到当前线程结果返回为止。

那怎么办呢,正确的使用方法应该是这样的,先创建一个对象存放线程需要保存的内容,就是上面出现过的DownloadFile:

public class DownloadFile {
	String fileName;//下载的文件名
	Date beginDate;//开始下载时间
	Date endDate;//结束下载时间

    ......省略get,set

	/**下载的时长(单位是毫秒)
	 * @return
	 */
	public long getDuration() {
		return (endDate.getTime()-beginDate.getTime());
	}

获取future值并展现出来 

private void downloadMeizitu(String url) {
			......
            //用一个list存放future对象
				List<Future<DownloadFile>> list = new ArrayList<Future<DownloadFile>>();

				for (Element element : imgs) {
					......
					Future<DownloadFile> future = downloadAsyncService.downloadImage(downloadFile,filePath, imgSrc,map);//多线程下图
					list.add(future);
				}
                //循环读取future的内容
				for(Future<DownloadFile> future:list) {
					logger.info("size=================="+String.valueOf(list.size()));
					while(true) {
						if(future.isDone()) {//线程执行完毕
							try {
								logger.info(future.get().getFileName()+"耗时"+future.get().getDuration()+"毫秒");
							} catch (InterruptedException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							} catch (ExecutionException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							break;
						}
					}
				}
				......

 

这样,执行完毕后就能够获取到每张图片的下载时间了:

2019-06-12 17:57:43.559  INFO 5476 --- [nio-8080-exec-1] c.skyblue.crawel.web.CrawlerController   : E:/youtube/images/zhainanfuli/17261耗时1251毫秒
2019-06-12 17:57:43.568  INFO 5476 --- [ taskExecutor-2] c.s.crawel.service.DownloadAsyncService  : *****http://ac.meijiecao.net/ac/img/znb/meizitu/20180123_meizitu_02.jpg
2019-06-12 17:57:43.569  INFO 5476 --- [nio-8080-exec-1] c.skyblue.crawel.web.CrawlerController   : E:/youtube/images/zhainanfuli/17261耗时1253毫秒
2019-06-12 17:57:43.570  INFO 5476 --- [nio-8080-exec-1] c.skyblue.crawel.web.CrawlerController   : E:/youtube/images/zhainanfuli/17261耗时1092毫秒
2019-06-12 17:57:43.571  INFO 5476 --- [nio-8080-exec-1] c.skyblue.crawel.web.CrawlerController   : E:/youtube/images/zhainanfuli/17261耗时906毫秒
2019-06-12 17:57:43.571  INFO 5476 --- [nio-8080-exec-1] c.skyblue.crawel.web.CrawlerController   : E:/youtube/images/zhainanfuli/17261耗时1078毫秒
2019-06-12 17:57:43.737  INFO 5476 --- [ taskExecutor-6] c.s.crawel.service.DownloadAsyncService  : *****http://ac.meijiecao.net/ac/img/znb/meizitu/20180123_meizitu_06.jpg
2019-06-12 17:57:43.738  INFO 5476 --- [nio-8080-exec-1] c.skyblue.crawel.web.CrawlerController   : E:/youtube/images/zhainanfuli/17261耗时1421毫秒
2019-06-12 17:57:43.872  INFO 5476 --- [ taskExecutor-7] c.s.crawel.service.DownloadAsyncService  : *****http://ac.meijiecao.net/ac/img/znb/meizitu/20180123_meizitu_07.jpg
2019-06-12 17:57:43.873  INFO 5476 --- [nio-8080-exec-1] c.skyblue.crawel.web.CrawlerController   : E:/youtube/images/zhainanfuli/17261耗时1556毫秒
2019-06-12 17:57:43.873  INFO 5476 --- [nio-8080-exec-1] c.skyblue.crawel.web.CrawlerController   : E:/youtube/images/zhainanfuli/17261耗时914毫秒
2019-06-12 17:57:44.055  INFO 5476 --- [ taskExecutor-9] c.s.crawel.service.DownloadAsyncService  : *****http://ac.meijiecao.net/ac/img/znb/meizitu/20180123_meizitu_09.jpg
2019-06-12 17:57:44.055  INFO 5476 --- [nio-8080-exec-1] c.skyblue.crawel.web.CrawlerController   : E:/youtube/images/zhainanfuli/17261耗时1739毫秒

由于多线程的原因,下载完成的耗时日志内容和下载的内容混杂在了一起,这也是多线程正在执行的一个体现。

至此,我们不仅用多线程下了图片,而且还用future传递了多线程的内容。但多线程是个很复杂的事,超时怎么办,线程的监控怎么处理,都需要进一步研究。但我毕竟是个下妹子图的,对我要求不能太高,这一章就讲到这,那些高级内容等我在看妹子图的间隙再写吧。

源码地址

 

© 著作权归作者所有

wphmoon
粉丝 12
博文 35
码字总数 63753
作品 0
广州
高级程序员
私信 提问
透过妹子看本质:爬虫小问题,并发大学问-3在springboot2中自定义监控线程池

在上一章中我们终于用多线程把妹子图给抓下来了,但是网络环境是很不可控的,你很难判断你抓图的线程啥情况,现在是在努力干活呢,还是在消极怠工。这一章,我们用springboot2的actuator来监...

wphmoon
06/18
88
0
[分享] 用NodeJs做一个小爬虫,附源码!

前言 利用爬虫可以做很多事情,单身汉子们可以用爬虫来收集各种妹子情报,撩妹族们可以用爬虫收集妹子想要的小东西,赚大钱的人可以用来分析微博言论与股票涨跌的关系诸如此类的,简直要上天...

没错就是酱紫
2016/08/03
1K
0
除了不会生孩子 MATLAB 什么都会

Part 1 有趣的 MATLAB 如果你在暑假、有WIFI、有西瓜的空调房里不知道做什么,不如学下MATLAB吧,有了它,你可以什么都不需要! 知乎网友@邵恩真,示范了如何使用花式 MATLAB 玩游戏! 用 MA...

21ic电子网
2018/02/01
0
0
网站改版过后,网站权重下降怎么解决?

网站改版是一件网站发展的重大工作,网站改版能够赋予网站新的活力,但也伴随着许多“网站改版并发症”出现。这里所谓的“网站改版并发症”是指网站在改版过后出现的问题,比如网站排名下降、...

kmark
2017/06/26
147
0
医号馆:那些血常规里可能隐藏着流感的蛛丝马迹

话说今年的流感 来得比往年还要多一些 千万别小看它们 有时候就是生与死的距离 (截图来自北京市卫生和计划生育委员会官网) 那么,今天让我们一起来探讨一下流感儿童的血象特点尤其在不可能...

qq_41953174
2018/04/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Java获取当前时间的上一个月和下一个月,第一天和最后一天,任意时间的第一天和最后一天,任意时间上一个月和下一个月详解

/** * 获取当前时间 * * @param args */ public static String getNowTime() { Calendar cal = Calendar.getInstance(); SimpleDa......

botkenni
12分钟前
0
0
yum指定某repo进行软件安装

命令 # yum install XXX --enablerepo=YYY XXX:需要要安装的软件。 YYY:相应源的“repo id”。 注意:建议安装的时候尽量选择同一个源,因为不同的源安装的软件可能会有冲突。 eg:指...

Wybaron
13分钟前
0
0
学习记录(day12-container容器布局、侧边导航栏)

container容器布局、侧边导航栏 index.js import Vue from 'vue'import VueRouter from 'vue-router'Vue.use(VueRouter)const routes = [ { path:'/', redirect:'/studentL......

庭前云落
14分钟前
1
0
为什么你成不了数据分析高手?可能是缺少这个思维

在讲前三章基础思维的时候,有读者反映文章过于理论,没有实际的操作讲解,那么今天我就在讲解数据思维的同时也会附上案例教程,以供大家参考学习。 话不多说,我们今天要讲的是矩阵思维,其...

帆软
26分钟前
2
0
怎样绘制时间轴流程图?迅捷画图教你一招轻松解决

时间轴流程图就是通过一张图将不同年代发生的事情或者是多方面的事情串联起来,形成一个相对完整的记录体系,运用图文的形式呈现给用户,时间轴流程图可以应用不同的领域,让过去发生的事情更...

百因必有果
36分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部