线程池能帮助我们有效的管理线程,避免重复的创建销毁线程。
Executor框架提供的线程池
newFixedThreadPool():固定线程数量的线程池
newSingleThreadExecutor():返回一个只有一个线程的线程池
newCachedThreadPool():返回一个可根据实际情况调整线程数量的线程池
newSingleThreadScheduledExecutor():返回一个ScheduledExecutorService对象,线程池大小为1,可以在某个时间执行某种功能
newScheduledThreadPool():返回一个ScheduledExecutorService对象对象,但是这个线程池可以指定数量。
遇到的问题
在做安居客爬虫的时候,因为几个任务同时做,我要知道前一个任务完成没有,只有当前一个任务做完,我也取不到数据时这个任务才能停止。最开始我是直接每个任务5个线程一直跑,当这个任务的前一个任务计数为5时就这个任务就停止。但是这样就和没有使用线程池一样了。后来我找了几种方法,根据逻辑不同可以选择不同的方法。
倒计时器CountDownLatch
这个适合线程数量知道的情况
public class Test5 {
public static CountDownLatch count = new CountDownLatch(5);
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(5);
for(int i = 0; i<5; i++) {
pool.submit(new T5Thread());
}
pool.shutdown();
try {
count.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("完成");
}
}
class T5Thread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
Test5.count.countDown();
}
}
用线程池的isTerminated()方法看是否还有没有完成的任务
调用这个方法首先要调用shutdown()或者shutdownNow(),停止提交新的任务,不然调用isTerminated()一直为false。以安居客爬虫搜索城市和地区的代码为例
这是搜索城市的线程,将城市的链接添加到链接,当集合的大小的为1时唤醒搜索地区的线程。线程结束后将cityFlag设为false,标志城市搜索完成
public class CitySearchThread extends Thread {
public void run() {
String result = null;
result = HttpUtils.CreatHttpGet("https://www.anjuke.com/sy-city.html");
if (result != null) {
try {
Document doc = Jsoup.parse(result);
Element cityItmElement = doc.getElementsByClass("city-itm").first();
Element lettercityElement = cityItmElement.getElementsByClass("letter_city").first();
Element ulElement = lettercityElement.getElementsByTag("ul").first();
Elements liElements = ulElement.getElementsByTag("li");
for (int i = 0; i < liElements.size() - 1; i++) {
Element cityListElement = liElements.get(i).getElementsByClass("city_list").first();
Elements cityElements = cityListElement.getElementsByTag("a");
for (int j = 0; j < cityElements.size(); j++) {
Element cityElement = cityElements.get(j);
String url = cityElement.absUrl("href");
synchronized (DataList.cityList) {
DataList.cityList.add(url);
if (DataList.cityList.size() == 1) {
DataList.cityList.notifyAll();
}
}
}
}
System.out.println("城市搜索完成");
}catch (Exception e) {
System.out.println("网页被拦截了");
}finally {
// 标志搜索所有的城市完成
DataList.cityFlag = false;
}
}else {
// 标志搜索所有的城市完成
DataList.cityFlag = false;
System.out.println("城市搜索完成");
}
}
}
用一个线程从城市的集合里面去取链接,取到以后就像线程池中添加一个解析那个链接的任务。当添加完以后就调用pool.shutdown(),这时就隔一段时间去看线程池任务执行完没有。执行完以后就把districtFlag设为false,标志地区搜索完成。
public class DistrictSearchThread extends Thread {
public void run() {
ExecutorService pool = Executors.newFixedThreadPool(5);
boolean flag = true;
while (flag) {
String url = null;
synchronized (DataList.cityList) {
try {
url = DataList.cityList.getFirst();
DataList.cityList.removeFirst();
} catch (Exception e) {
}
if (url == null) {
if (DataList.cityFlag == false) {
flag = false;
} else {
try {
DataList.cityList.wait(1000);// 防止最后一直等待,没有唤醒他
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
pool.execute(new SearchUtil(url));
}
pool.shutdown();
while (true) {
if (pool.isTerminated()) {
DataList.districtFlag = false;
System.out.println("地区搜索完成");
break;
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class SearchUtil extends Thread {
private String url;
public SearchUtil(String url) {
super();
this.url = url;
}
@Override
public void run() {
String result = null;
// 当线程是刚唤醒的那str是null
if (url != null) {
result = HttpUtils.CreatHttpGet(url);
if (result != null) {
Document doc = Jsoup.parse(result);
try {
Element contentElement = doc.getElementById("content_Rd1");
Element detailsfloat_lElement = contentElement.getElementsByClass("details float_l").get(1);
Element areasElement = detailsfloat_lElement.getElementsByClass("areas").first();
Elements aElements = areasElement.getElementsByTag("a");
for (int i = 0; i < aElements.size(); i++) {
Element cityElement = aElements.get(i);
String districtUrl = cityElement.absUrl("href");
synchronized (DataList.districtList) {
DataList.districtList.add(districtUrl);
if (DataList.districtList.size() == 1) {
DataList.districtList.notifyAll();
}
}
}
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + "=====区域:" + url + "无相关数据");
}
}
}
}
}
}
这样一层一层的嵌套多个线程就可以协同工作程序完成以后也能自动退出。