线程池

1.为什么要用线程池

线程池:管理线程的池子。

降低资源损耗和提高响应速度,因为线程也是对象,不断的创建和销毁对象损耗性能,我先准备几个线程对象,不用需要一个线程创建一个线程,提高响应速度。

重复利用,线程用完,还给池子,达到重复利用,节约资源。

线程执行流程

判断核心线程池是否已满,没满,创建核心线程去执行任务,
核心线程数满了,再看任务队列是否已满,没满,线程加入到任务队列中等待执行
任务队列满了,判断线程数是否达到最大线程数量,没达到,则创建一个非核心线程执行提交的任务。
达到最大线程数量,采用拒绝策略。

在这里插入图片描述

四种拒绝策略

抛异常(默认)
直接丢弃任务
丢弃队列里最老的任务,将当前任务继续提交给线程池。
交给线程池所在的线程进行处理

线程池参数,各个参数的作用,如何进行的

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,
   BlockingQueue<Runnable> workQueue,
   ThreadFactory threadFactory,
   RejectedExecutionHandler handler)
  • 核心线程池数 corePoolSize
    默认情况下,线程池中没有线程,只有等待任务到来才创建线程,当线程数达到核心线程数时,就会将线程加入到任务队列中

  • 最大线程池数 maximumPoolSize
    允许创建的最大线程数,如果任务队列满了,并且已创建的线程数小于最大线程数,则线程池会创建新的线程去执行任务,当已达到最大线程数,就会拒绝。

  • 线程保持时间:keepAliveTime
    线程池的工作线程空闲后,保存存活的时间。如果任务较多,每个任务执行的时间比较短,可以让线程的保存时间长一点。

  • unit: 参数keepAliveTime的时间单位。天,小时,分钟,还秒,微秒等

  • 任务队列 workQueue
    保存等待执行任务的阻塞队列。

  • ArrayBlockingQueue:数组结构的有界阻塞队列,先进先出

  • LinkedBlockingQueue:链表结构的有界阻塞队列,先进先出,吞吐量高于ArrayBlockingQueue。- Executors.newFixedThreadPool()使用了这个队列。

  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则一直处于阻塞状态。
    -PriorityBlockingQueue:一个具有优先级的无界阻塞队列。

  • threadFactory:创建线程的工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字

  • handler:拒绝策略。策略默认情况下是 AbortPolicy,表示无法处理新任务时抛出异常。

抛异常(默认)
直接丢弃任务
丢弃队列里最老的任务,将当前任务继续提交给线程池。
交给线程池所在的线程进行处理

结合执行流程。

哪几种任务对列

ArrayBlockingQueue:数组结构的有界阻塞队列,先进先出

LinkedBlockingQueue:链表结构的有界阻塞队列,先进先出,容量可设,不设的话,是一个无边界的阻塞队列。newFixedThreadPool线程池使用了这个队列

PriorityBlockingQueue:具有优先级的无界阻塞队列

DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。

SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则一直处于阻塞状态。newCachedThreadPool线程池使用了这个队列。

重要方法

ThreadPoolExecutor 类中有几个非常重要的方法:
excute() 覆写了 Executor 中声明的方法,向线程池提交任务,由线程池去执行
submit()向线程池中提交任务,能够返回任务执行的结果,内部调用的excute()方法,利用了future来获取任务执行结果。
shutdown()和shutNow()关闭线程池。

excute()和submit()区别

excute()方法用于提交不需要返回值的任务,无法判断任务是否被线程成功执行与否、
submit()方法用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过future对象可以判断任务是否执行成功。并且通过get()方法可以获取返回值。get()方法会阻塞当前线程直到任务结束,或get(Long timeout, TimeUnit unit)方法会阻塞当前线程一段时间后立即返回,这时候任务可能没有执行完。

threadsPool.execute(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
            }
        });

Future<Object> future = executor.submit(harReturnValuetask);
try {
     Object s = future.get();
} catch (InterruptedException e) {
    // 处理中断异常
} catch (ExecutionException e) {
    // 处理无法执行任务异常
} finally {
    // 关闭线程池
    executor.shutdown();
}

线程池的关闭

shutdown或shutdownNow,原理都是遍历线程池中工作线程,调用线程的intrupt()方法中断线程,线程有可能无法响应中断,永远无法停止。通常用shutdown关闭线程池,如果任务不一定要执行完,则调用shutdownNow.

区别:shutdownNow首先将线程池状态设为STOP,然后尝试停止所有的正在执行或暂停任务的线程,返回等待执行任务的列表。而shutdown只是将线程池的状态设置为SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。

只要调用了这两个关闭方法的其中一个,isShutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。

创建线程

阿里规范守则,不允许使用Executors创建线程,因为创建的线程没有大小限制,很容易造成资源耗尽,应该使用ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则

Executors弊端

FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积
大量的请求,从而导致OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能
会创建大量线程,从而导致OOM。

方法一 构造方法实现

在这里插入图片描述

方式二:通过Executor 框架的工具类Executors来实现 我们可以创建三种类型的ThreadPoolExecutor:

FixedThreadPool :线程池数量固定,有空闲的线程则让空闲线程去执行,若没有,则加入到任务对列,等待有空闲的线程。CPU密集型的任务,CPU在长期被工作线程使用的情况下,使用长期任务。

SingleThreadExecutor: 只有一个线程的线程池,若有空闲线层,让空闲线程执行任务,若没有,则加入任务队列等待执行。串行执行的任务,一个一个的执行

CachedThreadPool:根据实际情况调整线程池数量,有空闲则用空闲,没有则创建线程,空闲线程达到时间会自动停止。执行并发大量短期的小任务

newScheduledThreadPool:最大线程数为Integer.MAX_VALUE,线程从DelayQueue中获取time大于等于当前之间的任务, 执行完这个task后设置下次执行时间并放回到DelayQueue队列中。 周期性执行任务的场景,需要限制线程数量的场景

newFixedThreadPool (固定数目线程的线程池) 队列长度无限 CPU密集型的任务,CPU在长期被工作线程使用的情况下,使用长期任务
newCachedThreadPool(可缓存线程的线程池) 队列长度为1 执行并发大量短期的小任务
newSingleThreadExecutor(单线程的线程池) 队列长度无限 串行执行的任务,一个一个的执行
newScheduledThreadPool(定时及周期执行的线程池) 周期性执行任务的场景,需要限制线程数量的场景

使用无界队列的线程池会导致内存飙升吗?

会,newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行执行比较长,会导致队列的任务越积越多。导致即内存使用不停的飙升,最终导致OOM

线程池状态

RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED。
在这里插入图片描述
//线程池状态
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;

RUNNING
接收新任务,处理阻塞队列中的任务
调用线程池的shutdown()方法,可以切换到SHUTDOWN状态
调用线程池的shutdownNow()方法,可以切换到STOP状态

SHUTDOWN 不管要管正在执行的任务,还要管队列中的任务,只是不接受新任务,很负责
线程池不会接收新任务,但会处理阻塞队列中的任务
队列为空,且线程池中的任务也为空,进入TIDYING状态

STOP 巴不得快点结束,尝试中断正在执行的任务,不管队列中的任务,正在执行的任务搞完后就结束
不会接收新任务,也不会处理阻塞队列中ed任务,而且会中断正在执行的任务
线程中执行任务为空,进入TIDYING状态

TIDYING
所有的任务已经运行终止,记录的任务数量为0
terminated()执行完毕,进入TERMINATED状态

TERMINATED
线程池彻底终止

线程池异常处理

当使用线程池处理任务的时候,任务代码里面可能会抛出RuntimeException,抛出异常后,线程池可能捕获他,也可能创建一个新的线程处理它,这样我们不知道我们创建的任务出现了一样,所以我们要考虑线程池异常的情况。

例如:我们的任务中会抛出异常

ExecutorService threadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            threadPool.submit(() -> {
                System.out.println("current thread name" + Thread.currentThread().getName());
                Object object = null;
                System.out.print("result## "+object.toString());
            });
        }

在这里插入图片描述
虽然没有结果,但是没有抛出异常,所以我们没办法知道我们的代码是否会抛出异常。需要添加try/catch

在这里插入图片描述
线程处理了异常,我们可以直接用try/catch捕获。

线层池exec.submit(runnable)执行流程

在这里插入图片描述
1.try…catch捕获异常
2.submit执行,Future.get接收异常

作者:老刘
链接:https://zhuanlan.zhihu.com/p/73990200
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值