1、初始化线程池的4种方式
1 继承 Thread类
2 实现 Runnable 接口,并重写run方法
3 实现 Callable 接口 + FutureTask (可以拿到返回结果,可以处理异常)
4 线程池
区别:1、2不能得到返回值,3可以获取返回值
1、2、3都不能控制资源
4可以控制资源,性能稳定
@Slf4j
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
log.info("mian start............");
/**
* 1)、继承 Thread
* 2)、实现 Runnable 接口
* 3)、实现 Callable 接口 + FutureTask (可以拿到返回结果,可以处理异常)
* 4)、线程池
*/
// 1.继承 Thread
log.info("mian start............");
Thread01 thread01 = new Thread01();
thread01.start(); // 启动线程
log.info("mian end............");
// 2、实现runnable接口
/*
Runnable01 runnable01 = new Runnable01();
new Thread(runnable01).start();
*/
// 3、实现 Callable 接口 + FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(new Callable01());
new Thread(futureTask).start();
// 阻塞等待整个线程执行完,获取返回结果
Integer integer = futureTask.get();
log.info("mian end............" + integer);
// 线程池 给线程池直接提交任务
/**
* 以上三种方式启动线程的方式缺点是比较吃cpu资源
* 【将所有的多线程异步任务都交给线程池执行】
*/
}
public static class Thread01 extends Thread{
@Override
public void run() {
log.info("当前线程:" + Thread.currentThread().getId());
int i = 10/2;
log.info("运行结果:{}",i);
}
}
public static class Runnable01 implements Runnable{
@Override
public void run() {
log.info("当前线程:" + Thread.currentThread().getId());
int i = 10/2;
log.info("运行结果:{}",i);
}
}
public static class Callable01 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
log.info("当前线程:" + Thread.currentThread().getId());
int i = 10/2;
log.info("运行结果:{}",i);
return i;
}
}
}
2、线程池
1、线程池的创建
1)new ThreadPoolExecutor()
/**
七大参数
* int corePoolSize:核心线程数【一直存在】,线程池,创建好以后就准备就绪的线程数量,就等待来接收异步任务去执行
* int maximumPoolSize:最大线程数,作用:控制资源
* long keepAliveTime:存活时间,如果当前的线程数量大于核心数量。释放空闲的线程(超出核心线程的线程部分,核心线程是不释放的),只要线程空闲大于指定的keepAliveTime
* TimeUnit unit:时间单位
* BlockingQueue<Runnable> workQueue:阻塞队列,如果任务有很多,就会将目前多的任务放在队列里面
* 只要有线程空闲,就会去队列里面取出新的任务继续执行
* ThreadFactory threadFactory:线程的创建工厂,一般默认使用:Executors.defaultThreadFactory()
* RejectedExecutionHandler handler:如果队列满啦,按照我们指定的拒绝策略执行任务
**/
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5,
200,
10,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
- 使用Executors直接创建的线程池
1) Executors.newCachedThreadPool(); core是0,所有都可回收
2) Executors.newFixedThreadPool(10); 固定大小,core=max,都不可回收
3) Executors.newScheduledThreadPool(10); 定时任务的线程池
4) Executors.newSingleThreadExecutor(); 单线程的线程池,后台从队列里面获取任务,一个一个执行
2、线程池的工作流程
流程图:
流程步骤:
1、线程池创建,准备好核心线程,准备接收任务
2、任务存放顺序,先是判断核心数,如果小于核心数就存入核心线程池中
3、如果大于核心数,就会将进来的任务放入阻塞队列中,空闲的核心线程就会去阻塞队列中获取任务执行
4、当阻塞队列已满,就直接开新的线程执行,最大只能开到max指定的数量
5、max都执行完成,有很多空闲的线程,在指定的存活时间keepAliveTime以后,就会释放(max-core)这一部分的线程,核心线程不会释放
new LinkedBlockingDeque<>(100000): 默认是Integer的最大值,内存不足,需要在创建的时候指定大小
3、经典面试题
一个线程池 core 7; max 20 ,queue:50,100 并发进来怎么分配的;
* 先有 7 个能直接得到执行,接下来 50 个进入队列排队,在多开 13 个继续执行。现在 70 个
* 被安排上了。剩下 30 个默认拒绝策略。
4、使用线程池的好处
1) 降低资源的消耗
- 通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
2)提高响应速度
- 因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
3)提高线程的可管理性
- 线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配
5、详解各个参数
1)maximumPoolSize
线程池中的最大线程数。表示线程池中最多可以创建多少个线程,很多人以为它的作用是这样的:”当线程池中的任务数超过 corePoolSize 后,线程池会继续创建线程,直到线程池中的线程数小于maximumPoolSize“,其实这种理解是完全错误的。它真正的作用是:当线程池中的线程数等于 corePoolSize 并且 workQueue 已满,这时就要看当前线程数是否大于 maximumPoolSize,如果小于maximumPoolSize 定义的值,则会继续创建线程去执行任务, 否则将会调用去相应的任务拒绝策略来拒绝这个任务。另外超过 corePoolSize的线程被称做"Idle Thread", 这部分线程会有一个最大空闲存活时间(keepAliveTime),如果超过这个空闲存活时间还没有任务被分配,则会将这部分线程进行回收。
2)workQueue
阻塞队列。如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到该队列当中,注意只要超过了 corePoolSize 就会把任务添加到该缓存队列,添加可能成功也可能不成功,如果成功的话就会等待空闲线程去执行该任务,若添加失败(一般是队列已满),就会根据当前线程池的状态决定如何处理该任务(若线程数 < maximumPoolSize 则新建线程;若线程数 >= maximumPoolSize,则会根据拒绝策略做具体处理)。
常用的阻塞队列有:
1)ArrayBlockingQueue // 基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue // 基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue // 这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
3)threadFactory(线程工厂)
线程工厂。用来为线程池创建线程,当我们不指定线程工厂时,线程池内部会调用Executors.defaultThreadFactory()创建默认的线程工厂,其后续创建的线程优先级都是Thread.NORM_PRIORITY。如果我们指定线程工厂,我们可以对产生的线程进行一定的操作。
4)handler(拒绝执行策略)
拒绝执行策略。当线程池的缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
-
丢弃任务并抛出RejectedExecutionException异常:ThreadPoolExecutor.AbortPolicy;
-
丢弃任务,但是不抛出异常:ThreadPoolExecutor.DiscardPolicy;
-
丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程):ThreadPoolExecutor.DiscardOldestPolicy;
-
由调用线程处理该任务:ThreadPoolExecutor.CallerRunsPolicy