
当我们需要实现并发、异步等 *** 作时,可以使用ThreadPoolExecutor。
ThreadPoolExecutor
线程池:
系统中,我们创建(extend Thread/implement Runnable)、销毁(正常run方法完成后线程终止)线程的代价是比较高昂的。
如果频繁地创建和销毁进程,会大大降低系统运行效率和吞吐量。
线程池使得线程可以被复用,避免了线程频繁创建和销毁的开销,提高系统的运行效率和吞吐量。
实例
ThreadPoolExecutorexecute(new Runnable () {});
相关概念:
Task任务:new Runnable () {}
任务就是一个Runnable的对象,任务的执行方法就是该对象的run方法。
缓冲队列:workQueue
一个阻塞队列。
BlockingQueue<Runnable> workQueue;
corePoolSize:核心线程数
核心线程会一直存活,即使没有任务需要执行。
当线程数小于核心线程数时(还未满,就会一直增),即使有线程空闲,线程池也会优先创建新线程处理。
maxPoolSize:最大线程数
当线程数大于corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务,直到线程数量达到maxPoolSize。
execute(Runnable)
通过execute将一个任务交由线程池管理。
当一个任务通过execute方法欲添加到线程池时,线程池采用的策略如下(即添加任务的策略):
1、如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2、如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
3、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
4、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过handler所指定的策略来处理此任务。
如下图:
希望对您有所帮助!~
下面写个小程序测试一下。
private Runnable runnable = new Runnable() { @Override
public void run() {
List<Book> bookList = new ArrayList<>(); for (int i = 0; i < 5000; i++) {
Book book = new Book();
booksetUuid(UUIDrandomUUID()toString());
booksetName("name"); //其他set方法略
bookListadd(book);
} try {
Threadsleep(1000);
} catch (InterruptedException e) {
eprintStackTrace();
}
mBookDaoinsertOrReplaceInTx(bookList);
Logd(TAG, "插入book数据:" + bookListsize());
}
};private void insert() {
Logd(TAG, "线程池开始");
mBookDaodeleteAll(); long time = SystemcurrentTimeMillis();
ExecutorService executorService = ExecutorsnewFixedThreadPool(3); for (int i = 0; i < 200; i++) {
executorServicesubmit(runnable);
}
executorServiceshutdown(); for (; ; ) { if (executorServiceisTerminated()) { break;
} try {
executorServiceawaitTermination(1, TimeUnitSECONDS);
} catch (InterruptedException e) {
eprintStackTrace();
}
}
Logd(TAG, "线程池完成:" + (SystemcurrentTimeMillis() - time) + "ms");
}
runnable任务模拟1秒从网络拉取5000条数据并插入DB,insert方法使用线程池执行runnable任务。
执行时间超过1000秒,查看内存占用超过180M。如果数据量更多,肯定会发生OOM,基本上可以定位是greenDAO的问题。现在需要在两个方面优化,一是寻找内存占用的原因,二是提高数据的插入速度。
查看内存堆
内存的占用随着insert的数据量越多而递增,从中间dump出java堆,得到hprof文件。注意这个文件不是标准格式,只能用AndroidStudio打开。
图1
右击文件导出标准的hprof文件,用更加强大的MAT分析。
图2
图3
看到IdentityScope占了一半内存,可以确定是greenDAO缓存了插入数据。
mBookDaoinsertOrReplaceInTx(bookList);mBookDaodetachAll();
greenDAO的缓存功能是有用的,没必要关闭,改成在插入数据后,调用一次detachAll,将identityScope清空。
public void detachAll() { if (identityScope != null) {
identityScopeclear();
}
}
重建索引
对表插入大量数据,如果中间没有涉及到业务,可以先失效索引,待插入完成后重建索引。
String sql = "drop index index_isbn";
mDbexecSQL(sql);
sql = "drop index index_publisherid";
mDbexecSQL(sql);
sql = "drop index index_author";
mDbexecSQL(sql);
插入数据前,drop掉表中的索引。没有见到greenDAO有 *** 作索引的方法,直接执行sql命令。
sql = "create index index_isbn on book(isbn)";
mDbexecSQL(sql);
sql = "create index index_publisherid on book(publisherid)";
mDbexecSQL(sql);
sql = "create index index_author on book(author)";
mDbexecSQL(sql);
插入数据完成后,重建索引。最后执行100w数据插入大约耗时450秒,比什么都不做快了两三倍。
异步 *** 作
上一个步骤的耗时包含了模拟网络和数据库 *** 作的时间,使用多线程将两个环节分离,可以减少总时间。
greenDAO提供了AsyncSession这个异步 *** 作类,使用daoSessionstartAsyncSession()获取实例,内部实现使用了线程池和阻塞队列,原理很简单不用多讲。
mAsyncSessionrunInTx(new Runnable() { @Override
public void run() {
mBookDaoinsertOrReplaceInTx(bookList);
mBookDaodeleteAll();
}
});
获取数据后,提交给AsyncSession异步插入数据库。要注意在合适地方使用waitForCompletion,等待AsyncSession完成已有任务。如果获取数据速度很快,而 *** 作数据库很慢,会导致过多数据缓存在AsyncSession的内部阻塞队列。
最后测试一下100w数据插入数据库,耗时不到150秒,又快了几倍。
作者:展翅而飞
链接:>
以上就是关于线程池-参数篇:2.队列全部的内容,包括:线程池-参数篇:2.队列、Java 中几种常用的线程池、线程池基本使用和ThreadPoolExecutor核心原理讲解等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)