Java 多线程(四)ThreadPoolExecutor 线程池各参数的意义
Java 多线程 系列文章目录:
- Java 多线程(一)线程间的互斥和同步通信
- Java 多线程(二)同步线程分组问题
- Java 多线程(三)线程池入门 Callable 和 Future
- Java 多线程(四)ThreadPoolExecutor 线程池各参数的意义
- Java 多线程(五)Lock 和 Condition 实现线程同步通信
- Java 多线程(六)Semaphore 实现信号灯
- Java 多线程(七)CyclicBarrier 同步的工具类
- Java 多线程(八)CountDownLatch 同步工具类
- Java 多线程(九)Exchanger 同步工具类
- Java 多线程(十)ArrayBlockingQueue 阻塞队列
- Java 多线程(十一)JDK 同步集合
为什么要使用线程池?
在 Android 开发中,一些耗时的操作都会放到后台线程去执行,比如:网络、本地文件、数据库等。如:
new Thread(new Runnable() {
@Override
public void run() {
//do something...
}
}).start();
每个线程至少消耗64k的内存,如果你在某个时间点,迅速开启了很多线程(比如加载列表图片,然后用户滑动列表),这个时候可能内存使用量就会飙升。可能会产生如下问题:
- 会出现内存抖动(memory churn),因为短时间开启了很多线程,完成任务后,这些线程都会被回收。内存表现为:低-高-低。甚至可能出现OOM。
- 一个系统所能处理的线程数量是有限的,如果超多了最大承载量,性能会受到很大的影响。而且可能还会影响用户的后续操作。
这时候Thread Pool线程池的作用就凸显出来了。
ThreadPoolExecutor 详解
Java 为我们提供了操作线程池的API: ThreadPoolExecutor ,该类实现了 ExecutorService 接口
JDK 中相关的线程池的类都实现了该接口。
创建一个线程池可以通过 ThreadPoolExecutor 类来实现:
ThreadPoolExecutor pool = new ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long
//新建一个线程池
keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
//执行任务
pool.execute(Runnable);
下面是官方对 ThreadPoolExecutor 的参数说明:
Parameters:
corePoolSize - the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
maximumPoolSize - the maximum number of threads to allow in the pool
keepAliveTime - when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
unit - the time unit for the keepAliveTime argument
workQueue - the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method.
下面对各个参数进行个解释:
- corePoolSize 核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
- maxPoolSize 线程池允许最大的线程数量。
- keepAliveTime 当线程空闲时间达到 keepAliveTime,该线程会退出,直到线程数量等于 corePoolSize。如果allowCoreThreadTimeout 设置为 true,则所有线程均会退出直到线程数量为0。
- allowCoreThreadTimeout 是否允许核心线程空闲keepAliveTime退出,默认值为false。
- workQueue 任务队列。pool.execute(runnable) 提交的 task都 会放到workQueue。
下面来一个简单的 sample:
public class MyClass {
private ThreadPoolExecutor pool ;
private MyClass(){
//创建线程池
pool = new ThreadPoolExecutor(4, 7, 60, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public static void main(String[] args) {
MyClass myClass = new MyClass();
for (int i = 0; i < 10; i++) {
//提交任务
myClass.pool.execute(new MyRunnable(myClass));
}
myClass.pool.shutdown();
}
private String getCount() {
return pool.getCorePoolSize()+"-"+pool.getActiveCount() + "-" + pool.getMaximumPoolSize();
}
private static class MyRunnable implements Runnable {
MyClass myClass;
MyRunnable(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
System.out.println("thread name:" + Thread.currentThread().getName() + " start " + myClass.getCount());
try {
//模拟耗时任务
Thread.sleep(3000L);
System.out.println("thread name:" + Thread.currentThread().getName() + " end " + myClass.getCount());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上面的代码很简单:创建了一个 corePoolSize 为 4,maxPoolSize 为 7 的线程池。
然后往线程池里提交 10 个任务,每个任务打印 :
pool.getCorePoolSize()+"-"+pool.getActiveCount() + “-” + pool.getMaximumPoolSize()
即 corePoolSize (核心线程数),activeCount (正在活动的线程总数) 和 maximumPoolSize (线程池允许的最大线程数) 值。
测试结果如下:
thread name:pool-1-thread-1 start 4-2-7
thread name:pool-1-thread-2 start 4-4-7
thread name:pool-1-thread-3 start 4-4-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-1 end 4-4-7
thread name:pool-1-thread-2 end 4-3-7
thread name:pool-1-thread-4 end 4-4-7
thread name:pool-1-thread-1 start 4-4-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-2 start 4-4-7
thread name:pool-1-thread-3 end 4-4-7
thread name:pool-1-thread-3 start 4-4-7
thread name:pool-1-thread-2 end 4-4-7
thread name:pool-1-thread-4 end 4-4-7
thread name:pool-1-thread-3 end 4-4-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-1 end 4-3-7
thread name:pool-1-thread-2 start 4-4-7
thread name:pool-1-thread-4 end 4-2-7
thread name:pool-1-thread-2 end 4-2-7
Process finished with exit code 0
从测试结果来看,我们打印 pool.getCorePoolSize()+"-"+pool.getActiveCount() + “-” + pool.getMaximumPoolSize() 的值是正常的,但是只创建了4个线程:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
我们设置了线程池的最大数为 7,我们提交了 10 个任务,但是为什么只创建了 corePoolSize = 4 个线程?
查看官方文档可以找到答案:
- 当通过 execute(Runnable) 提交一个新任务,并且小于 corePoolSize 正在运行的线程数,将会创建一个新的线程来处理这个任务,不管线程池里有没有空闲的线程。
- If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full. 意思是说:任务数量大于 corePoolSize,但是小于 maximumPoolSize,并且 workQueue 队列满了,才会创建新的线程。
- 如果 corePoolSize 和 maximumPoolSize 值设置成一样的,相当于创建了一个固定数量的线程池。
- 多数情况下,都是通过构造方法来设置 corePoolSize 和 maximumPoolSize,但是也可以通过 setCorePoolSize 和setMaximumPoolSize 来动态设置。
所以上面的例子,只创建了 4 个线程,因为虽然我们提交了 10 个任务,但是构建 workQueue 时候没有传入队列大小,默认大小是 Integer.MAX_VALUE,所以 workQueue 并没有满,所以最多就创建了4个线程。
据此,我们可以把 workQueue 队列容量改成 4 :
pool = new ThreadPoolExecutor(4, 7, 60, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(4));
测试结果如下所示:
thread name:pool-1-thread-1 start 4-2-7
thread name:pool-1-thread-2 start 4-2-7
thread name:pool-1-thread-3 start 4-3-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-5 start 4-6-7
thread name:pool-1-thread-6 start 4-6-7
thread name:pool-1-thread-1 end 4-6-7
thread name:pool-1-thread-2 end 4-6-7
thread name:pool-1-thread-2 start 4-5-7
thread name:pool-1-thread-1 start 4-6-7
thread name:pool-1-thread-3 end 4-6-7
thread name:pool-1-thread-3 start 4-6-7
thread name:pool-1-thread-4 end 4-6-7
thread name:pool-1-thread-5 end 4-6-7
thread name:pool-1-thread-4 start 4-6-7
thread name:pool-1-thread-6 end 4-5-7
thread name:pool-1-thread-1 end 4-4-7
thread name:pool-1-thread-2 end 4-4-7
thread name:pool-1-thread-3 end 4-2-7
thread name:pool-1-thread-4 end 4-1-7
Process finished with exit code 0
发现创建了 6 个线程,大于上一次的测试结果(上一次是创建了 4 个线程),可是我们设置的 maximumPoolSize = 7,按道理应该是创建 7 个线程才对呀,这是为什么呢?
这需要了解下 workQueue 队列的策略了。我们上面的列子使用的是 LinkedBlockingQueue。
下面来看看官方文档对 BlockingQueue的描述:
Any link BlockingQueue may be used to transfer and hold
submitted tasks. The use of this queue interacts with pool sizing:
If fewer than corePoolSize threads are running, the Executor
always prefers adding a new thread
rather than queuing.
If corePoolSize or more threads are running, the Executor
always prefers queuing a request rather than adding a new
thread.
If a request cannot be queued, a new thread is created unless
this would exceed maximumPoolSize, in which case, the task will be
rejected.
主要意思就是:
- 如果运行的线程少于 corePoolSize,Executor 会创建新线程来执行任务,不会把任务放进 queue。
- 如果运行的线程等于或多于 corePoolSize,Executor 将请求加入队列,而不是创建新的线程。
- 如果队列已满,无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
这样就能解释为什么只创建 6 个线程了。
总共有 10 个 task,核心线程 corePoolSize = 4,所以 3 个任务是不会放进 queue 的,直接创建 3 个新线程来处理 task 了,然后再执行 execute(Runnable) 的时候,就会大于等于 corePoolSize,所以就会把接下来的 4 个任务放进 queue (容量为4),然后就剩下 3 个 task 了,发现队列已经满了,创建 3 个线程来处理这剩下的 3 个 task,所以总共只创建 6 个线程了。
maximumPoolSize = 7,我就是想让它创建 7 个线程怎么办?我们知道了上面的原理就很简单了,可以把队列的最大容量改成 3 或者添加 11 个任务就可以了:
new LinkedBlockingQueue<Runnable>(3)
或者:
for (int i = 0; i < 11; i++) {
myClass.pool.execute(new MyRunnable(myClass));
}
运行结果如下所示:
thread name:pool-1-thread-1 start 0-1-7
thread name:pool-1-thread-2 start 0-2-7
thread name:pool-1-thread-3 start 1-4-7
thread name:pool-1-thread-4 start 4-5-7
thread name:pool-1-thread-5 start 4-7-7
thread name:pool-1-thread-6 start 4-7-7
thread name:pool-1-thread-7 start 4-7-7
.....
Java 提供了 3 种策略的 Queue:
- SynchronousQueue 直接传送task(Direct handoffs)
- LinkedBlockingQueue 无边界队列(Unbounded queues)
- ArrayBlockingQueue 有边界队列(Bounded queues)
JDK 提供的工厂类 Executors 来创建线程池
Java 也给我们提供了一些工厂方法来创建线程池:
- Executors.newFixedThreadPool(int threads);
- Executors.newCachedThreadPool();
- Executors.newSingleThreadExecutor();
- Executors.newScheduledThreadPool(int threads);
这些方法都是通过构建 ThreadPoolExecutor 来实现的,具体的细节可以去看看对应的实现,我们已经对 ThreadPoolExecutor 的构造函数的参数有了比较好的了解,在使用这些工具方法创建线程池时,一定要看看它里面是怎么实现的。如果都不满足你的需求,可以自己构造 ThreadPoolExecutor。
更多推荐
所有评论(0)