文章目录
1 线程池的特点
1.1 显式创建线程的缺点
在先前的学习中,对于多线程编程,都是在某个类中实现Runnable接口并实现run()方法,接着使用
new Thread(this).start();
来直接创建线程并执行,但这种方法受到了来自插件的警告,不要显式创建线程,使用线程池代替
在了解线程池前,先来看显式创建线程的缺点:
线程是资源的一种,显式地创建线程并销毁的过程必然要频繁使用内存,而内存是极为宝贵的资源,为此就需要使用线程池来替代
1.2 线程池及其优点
顾名思义,即管理存储线程的空间,使用线程池可以方便地管理线程,并减少对内存地消耗
使用线程池的优点:
1,减少内存资源的消耗
即通过重复使用已经创建的线程降低线程创建和消耗带来的损耗
2,提高请求的处理速度
当任务到达时,任务不需要等待线程创建完毕才能执行
3,提高线程的可管理性
线程是稀缺资源,不能无限制创建线程,使用线程池可以统一地分配,监控,优化
1.3 线程池的工作原理
2 线程池的实现方式
2.1 ThreadPoolExecutor类
线程池的真正实现方式即ThreadPoolExector类
它有四种构造方法,最主要的是:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
该构造方法创建一个ThreadPoolExecutor对象需要7个参数:
1: int corePoolSize
核心线程数(必要参数)
默认情况下,核心线程会一直存活
2: int maximumPoolSize
线程池所能容纳的最大线程数(必要参数)
超过该值后后续的新任务会被阻塞
3: long keepAliveTime
非核心线程存在时长(必要参数)
如果超过该值,非核心线程会被回收
4: TimeUnit unit
指定keepAliveTime的参数时间单位(必要参数)
可以是毫秒,秒,分
如TimeUnit.SECONDS
5: BlockingQueue<Runnable> workQueue
阻塞式任务队列(必要参数)
将Runnable的对象存储在队列中,如new ArrayBlockingQueue<Runnable>(3)
6: ThreadFactory threadFactory
线程工厂(可选参数)
指定为线程池创建新线程的方式
7: RejectedExecutionHandler handler
拒绝策略(可选参数)
当达到最大线程数时需要执行的策略
如:
handler的拒绝策略:
AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
DisCardPolicy:不执行新任务,也不抛出异常
DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
CallerRunsPolicy:直接调用execute来执行当前任务
2.2 创建线程池及关于线程池的方法
2.2.1 使用ThreadPoolExecutor类创建一个线程池
**
* @author 雫
* @date 2021/1/26 - 9:18
* @function 线程池创建测试
*/
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPool =
new ThreadPoolExecutor(5, 6, 60,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3));
Runnable task1 = new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 10; i++) {
System.out.println("任务1被" + i + "次执行");
}
}
};
threadPool.execute(task1);
Runnable task2 = new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 10; i++) {
System.out.println("任务2被" + i + "次执行");
}
}
};
threadPool.execute(task2);
Runnable task3 = new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 10; i++) {
System.out.println("任务3被" + i + "次执行");
}
}
};
threadPool.execute(task3);
threadPool.shutdown();
}
}
这里没有使用ThreadFactory和RejectedExecutionHandler参数,演示效果:
2.2.2 关于线程池的方法
//向线程池提交一个任务
threadPool.execute(task);
//设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdown();
// 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
threadPool.shutdownNow();
2.3 基于ThreadPoolExecutor几种线程池
CachedThreadPool:可缓存的线程池
该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大
当有需要时创建线程来执行任务,没有需要时回收线程
适用于耗时少,任务量大的情况
SecudleThreadPool:周期性执行任务的线程池
按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程
非核心线程的大小也为无限大
适用于执行周期性的任务。
SingleThreadPool:只有一条线程来执行任务
适用于有顺序的任务的应用场景。
FixedThreadPool:定长的线程池
有核心线程,核心线程的即为最大的线程数量,没有非核心线程
2.4 小结
在使用ThreadPoolExecutor类创建线程池时,插件给出了警告,应该给出创建线程方式的ThreadFactory参数
对于创建线程池有5种方式:
1,ThreadPoolExecutor
2,FixedThreadPool
3,SecudleThreadPool
4,SingleThreadPool
5,CachedThreadPool
最好使用ThreadPoolExecutor类来创建线程池,它的参数列表能够很清晰地看出该线程池的属性