前言
模板设计模式系列文章
模板设计模式(一) - 定义及源码中所用到的模板设计模式
模板设计模式(二) - 线程池工作机制
模板设计模式(三) - OkHttp的 Dispatcher(线程池)
场景
比如我们需要获取网络图片,就必须要开线程去下载图片,比如有100张,就得创建100个线程,线程开的太多,会影响效率和吞吐量
1. 线程的执行时间
- T = T1(线程的创建时间)+ T2(run方法的执行时间)+ T3(线程的销毁时间);
- 正如上边所说,如果我们下载100张图片,就需要开100个线程,这样做就相当于是线程的创建时间增大,线程的销毁时间增大,导致的结果就是每一个线程的执行时间增大,这个时候就引入线程池的概念;
2. 线程池工作机制如图所示
1>:线程池的好处:
- 解决了线程反复的创建和销毁,做到线程可以反复使用;
2>:线程池工作机制:
这里举例说明:就是100张图片放在线程池中下载;
1>:首先有一个线程池,里边可以放多个线程,比如放4个线程:线程一、线程二、线程三、线程四来下载100张图片;
2>:然后又一个缓存队列,就是下载队列,其实就是 Runnable,100个下载任务都在 这个缓存队列中;
3>:比如缓存队列中现在有5个任务,4个线程都会从缓存队列中去取下载任务,如果线程1先执行完任务,这个时候会去缓存队列中取任务:
a:如果缓存队列中有任务,就取出来下载;
b:如果没有下载任务,就等待着;
c:假设线程的存活时间是 60秒,如果等到30秒又有下载任务了,然后取出来下载,如果没有就继续等待,等到60秒,如果还是没有下载任务,线程1就被销毁;
线程2、线程3、线程4都是一样的,存活时间都是 60秒:
如果下载完,就从缓存队列中取下载任务,如果有就取出来下载,如果没有就等待,如果等到30秒,有下载任务了,就取出来下载,如果没有就继续等待,等到60秒,如果还是没有下载任务,线程2、线程3、线程4就全部被销毁;
如果线程1、线程2、线程3、线程4都被销毁了,这个时候线程池中没有线程了,如果缓存队列中又有新的下载任务,线程池就又会重新创建线程,然后从缓存队列中取任务来下载,流程和上边一样,存活时间都是60秒
3. 线程池示例代码如下
1>:下边是一次性下载 20个任务,缓存队列的sPoolWorkQueue 设置的值是 128,结果是正常的,没有报错的
/**
* Email: [email protected]
* Created by Novate 2018/5/13 10:47
* Version 1.0
* Params:
* Description: 线程池示例代码
*/
public class ThreadPoolTest {
static ThreadPoolExecutor threadPoolExecutor;
// 线程队列,就是缓存队列
private static final BlockingQueue sPoolWorkQueue =
new LinkedBlockingQueue(128);
static {
threadPoolExecutor = new ThreadPoolExecutor(
4, // 核心线程数:就是线程池中的线程数量,自己图中画了 4个线程,这里就是4
10, // 最大线程数:就是线程池中最大线程数量,随便给个10
60, // 线程存活时间:比如线程1执行完下载任务,然后缓存队列中没有任务了,这个时候线程1的等待时间,如果等了60秒还没有下载任务,就销毁线程
TimeUnit.SECONDS, // 线程存活时间的单位:秒
sPoolWorkQueue, // 线程的队列,就是图中的缓存队列,给队列中放 下载任务的个数,比如给缓存队列中放 4个下载任务
new ThreadFactory() { // 线程的创建工厂,如果线程池需要创建线程,就调用这个new Thread()来创建
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = new Thread(r,"自己线程的名字");
thread.setDaemon(false); // 不是守护线程
return new Thread(r);
}
});
}
public static void main(String[] args){
/*// 原来写法:直接new Thread
testThread() ;*/
// 线程池
testThreadPool();
}
private static void testThreadPool() {
// 下边代码意思就是:
// 一次性给 缓存队列中加入20个下载任务;
// 一次性只会执行4个下载任务;执行5次,然后就等待60秒,如果60秒之内继续给 缓存队列中放下载任务,线程池的线程就继续下载
// 如果60秒内没有下载任务,就会销毁这4个线程;
// 如果过了一会,缓存队列中又有下载任务了,这个时候线程池又会重新创建线程,然后从缓存队列中取任务,然后下载
for (int i = 0; i < 20; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("图片下载完毕" + Thread.currentThread().getName());
}
} ;
// 这个方法不会立马执行,会先把runnable任务加入缓存队列,寻找合适的时机去执行
threadPoolExecutor.execute(runnable);
}
}
/**
* 原来写法:直接new Thread
*/
private static void testThread() {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
}
2>:如果缓存队列的sPoolWorkQueue 设置的值是 4,结果就会报错
- 报错原因如下:
1>:线程队列(缓存队列)是4,核心线程数是4,最大线程数是10;
2>:目前加入的 runnable 数量有20个,20个runnable 放到缓存队列中,但是如果设置sPoolWorkQueue=4,表示缓存队列中只能放4个runnable,剩余的16个不能放下,这个时候最大线程数是10,非核心线程数 是6(最大线程数 - 核心线程数);
3>:这个时候会拿6个 runnable出来执行,此时就会在线程池中重新创建6个线程,线程池中的线程就达到10个;
4>:但是还有10个runnable不能放到 缓存队列中,就意味着 剩余的10个 runnable没有办法执行,这个时候就会报异常;
4. Queue 的参数
1>:BlockingQueue:
先进先出的队列,FIFO,先进去的先执行(RxJava所使用的)
2>:SynchronousQueue:
线程安全的队列,它里边没有固定的缓存的(OkHttp所用的)
3>:PriorityBlockingQueue:
无序的可以根据优先级进行排序 ,指定的对象要实现 Comparable 作比较 ,下边使用testRequest()方法来测试
a:首先自己写一个 Request来实现 Runnable和Comparable;
b:然后在写一个 for循环,创建 a 中的 Request对象,然后调用 线程池的 execute()方法,把 Request对象传递进去就可以,代码如下:
- Request代码如下:
/**
* Email: [email protected]
* Created by Novate 2018/5/13 11:43
* Version 1.0
* Params:
* Description: 测试PriorityBlockingQueue ,自己定义的 Request对象,然后实现 Runnable,Comparable接口
*/
public class Request implements Runnable,Comparable{
@Override
public void run() {
System.out.println("run");
}
/**
* 用来处理排序的
* 这里只可以返回 <0、=0、>0
*/
@Override
public int compareTo(@NonNull Request o) {
return 0;
}
}
- ThreadPoolTestException测试类如下:
/**
* Email: [email protected]
* Created by Novate 2018/5/13 10:47
* Version 1.0
* Params:
* Description:
*/
public class ThreadPoolTestException {
static ThreadPoolExecutor threadPoolExecutor;
private static final BlockingQueue sPoolWorkQueue =
new LinkedBlockingQueue(4);
// sPoolWorkQueue 修改为4 以后就报错,原因就是:
// RejectedExecutionException
// 线程队列(缓存队列)是4,核心线程数是4,最大线程数是10,目前加入的 runnable 数量有20个
// 20个runnable 放到缓存队列中,但是如果设置sPoolWorkQueue=4,表示缓存队列中只能放4个runnable,剩余的16个不能放下,
// 这个时候最大线程数是10,非核心线程数 是6(最大线程数 - 核心线程数),这个时候会拿6个 runnable出来执行,此时就会在线程池中
// 重新创建6个线程,线程池中的线程就达到10个,但是还有10个runnable不能放到 缓存队列中,就意味着 剩余的10个 runnable没有办法执行
// 这个时候就会报异常
static {
threadPoolExecutor = new ThreadPoolExecutor(
4, // 核心线程数:就是线程池中的线程数量,自己图中画了 4个线程,这里就是4
10, // 最大线程数:就是线程池中最大线程数量,随便给个10
60, // 线程存活时间:比如线程1执行完下载任务,然后缓存队列中没有任务了,这个时候线程1的等待时间,如果等了60秒还没有下载任务,就销毁线程
TimeUnit.SECONDS, // 线程存活时间的单位:秒
sPoolWorkQueue, // 线程的队列,就是图中的缓存队列,给队列中放 下载任务的个数,比如给缓存队列中放 4个下载任务
new ThreadFactory() { // 线程的创建工厂,如果线程池需要创建线程,就调用这个new Thread()来创建
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = new Thread(r,"自己线程的名字");
thread.setDaemon(false); // 不是守护线程
return new Thread(r);
}
});
}
public static void main(String[] args){
testRequest() ;
}
/**
* 测试 Request
*/
private static void testRequest() {
for (int i = 0; i < 20; i++) {
Request request = new Request() ;
threadPoolExecutor.execute(request);
}
}
}