为什么要使用线程池

1 线程复用 控制最大并发数 管理线程
2 降低消耗:可以直接从线程 中取出线程,避免创建新线程时的消耗
3 提高响应速度:当任务到达时,不需要等待线程的创建
4 提高线程的可管理性:如果随意创建多个线程,会浪费系统资源。使用线程池可以统一分配管理。

线程池的运行流程

JAVA中线程池的使用demo java线程池怎么使用_java

①如果在线程池中的线程数量没有达到核心的线程数量,这时候就会启动一个核心线程来执行任务。(即优先使用核心线程)。
②如果线程池中的线程数量已经超过核心线程数,这时候任务就会被插入到任务队列中排队等待执行。
③由于任务队列已满,无法将任务插入到任务队列中。这个时候如果线程池中的线程数量没有达到线程池所设定的最大值,那么这时候就会立即启动一个非核心线程来执行任务。
④如果线程池中的数量达到了所规定的最大值,那么就会拒绝执行此任务,这时候就会调用RejectedExecutionHandler中的rejectedExecution方法来通知调用者。

线程池的三种实例

线程池的底层是就ThreadPoolExecutor

1 newFixedThreadPool

ExecutorService pool =  Executors.newFixedThreadPool(3)

创建一个线程数量固定的线程池

适用于长期执行任务

底层使用阻塞队列 LinkedBlockingQueue

只有核心线程

JAVA中线程池的使用demo java线程池怎么使用_多线程_02

2 newSingleThreadExecutor

创建只有一个线程的线程池

适合任务是一个接一个地来

JAVA中线程池的使用demo java线程池怎么使用_spring_03

3 Executors.newCachedThreadPool();

创建一个可以动态扩容的线程池

适合执行很多短期异步的任务

全部为非核心线程

4

JAVA中线程池的使用demo java线程池怎么使用_面试_04

4 线程池的使用实例

class MyCallable implements Callable<String>{
    @Override
    public String call() throws Exception {
        String info = Thread.currentThread().getName() + " come in call get " + UUID.randomUUID().toString().substring(0, 6);
      //  System.out.println(info);
        return info;
    }
}

public class ThreadPoolTest {
    public static void test() {
      ExecutorService pool =  Executors.newFixedThreadPool(3);
//       ExecutorService pool = Executors.newSingleThreadExecutor();
//        ExecutorService pool = Executors.newCachedThreadPool();
        try {
            for (int i = 0; i < 10; i++) {
                FutureTask<String> ft = new FutureTask<>(new MyCallable());
                pool.submit(ft);
                while(!ft.isDone()){}
                System.out.println(ft.get());
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }

线程池的七大参数

我们在实例化一个线程池时,底层调用是TrheadPoolExecutor

JAVA中线程池的使用demo java线程池怎么使用_多线程_05


1 核心线程数 corePoolSize :线程池中长驻的线程数量。该类型线程不会被销毁

2 线程最大值 maximumPoolSize:线程池中能容纳的线程数。包括核心和非核心的

3 阻塞队列 workQueue:任务队列。存放已提交但还未执行的任务

4 keepAliveTime:非核心线程可以空闲的时间

5 unit: keepali vetime的单位

6 threadFactory: 生成线程的工厂,一般为默认值

7 拒绝策略 rejectedExecutionHandler:当核心线程已经使用 等待队列已满且线程数量已到达最大时,开启拒绝策略

JAVA中线程池的使用demo java线程池怎么使用_面试_06

四个拒绝策略

1 AbortPolicy:直接抛出RejectedExecutionExecption异常阻止系统正常运行
2 CallerRunsPolicy:既不会放弃任务,也不会抛出异常,而是将任务回退到调用者,即让调用者执行这个任务。
3 DiscardOldertPolicy: 抛出队列中等待最久的任务,然后把新任务添加到队列中
4 DiscardPolicy: 直接抛弃任务。但不抛出异常。最好的方法

线程池使用上的优化

1 使用自定义的LinkedBockingQueue

因为默认的线程池底层使用的阻塞队列为LinkedBlockingQueue,该队列的可容纳的元素太多,会造成OOM(内存用完)。

通过ThreadPoolExecutor来自定义线程池 在实例化LinkedBlockingQueue时,设置该队列的容量,并设置拒绝策略为直接抛弃。

ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 3L,
        TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.DiscardPolicy());

2 线程池中线程数量的最大值

1 CPU密集型:CPU一直全速运行。此时应设置线程数较少,为CPU核心数+·1.
2 IO密集型:可以多分配一点线程数量
CPU核心数 * 2
CPU核心数 / (1 - 阻塞系数) 阻塞系数为0.8~0.9

获取CPU核心数的方式

Runtime.getRuntime().availableProcessors()