线程池-参数篇:2.队列

线程池-参数篇:2.队列,第1张

当我们需要实现并发、异步等 *** 作时,可以使用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核心原理讲解等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9632251.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-30
下一篇2023-04-30

发表评论

登录后才能评论

评论列表(0条)

    保存