什么是线程池

参考答案

1. 线程池的概念

线程池是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。

2. 使用线程池的原因

因为 Java 中创建一个线程,需要调用操作系统内核的 API,操作系统要为线程分配一系列的资源,成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。
使用线程池就能很好地避免频繁创建和销毁。

线程池是一种生产者——消费者模式,下面是一个Java 线程池的代码:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;

public class ThreadPool {
    
    //阻塞队列实现生产者-消费者
    BlockingQueue<Runnable> taskQueue;
    
    //工作线程集合
    List<Thread> threads = new ArrayList<Thread>();
    
    //线程池的构造方法
    ThreadPool(int poolSize, BlockingQueue<Runnable> taskQueue) {
        this.taskQueue = taskQueue;
        
        //启动线程池对应 size 的工作线程
        for (int i = 0; i <poolSize; i++) {
            Thread t = new Thread(() -> {
                while (true) {
                    Runnable task;
                    try {
                        task = taskQueue.take();//获取任务队列中的下一个任务
                        task.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
            threads.add(t);
        }
    }
    
    //提交执行任务
    void execute(Runnable task) {
        try {
            //把任务方法放到任务队列
            taskQueue.put(task);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

线程池的使用测试:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class TestThreadPool {
    public static void main(String[] args) {
        // 创建有界阻塞任务队列
        BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>(10);
        // 创建 3个 工作线程的线程池
        ThreadPool tp = new ThreadPool(3, taskQueue);
        
        //提交 10 个任务
        for (int i = 1; i <= 10; i++) {
            final int j = i;
            tp.execute(() -> {
                System.out.println("执行任务" + j);
            });
        }
    }

}

结果:

执行任务1
执行任务2
执行任务3
执行任务6
执行任务5
执行任务4
执行任务8
执行任务7
执行任务10
执行任务9

这个线程池的代码中:

  • poolSize 是线程池工作线程的个数。
  • BlockingQueue taskQueue 是用有界阻塞队列存储 Runnable 任务。
  • execute(Runnable task) 提交任务。
  • 线程池对象被创建,就自动启动 poolSize 个工作线程。
  • 工作线程一直从任务队列 taskQueue 中取任务。

线程池的原理就是这么简单,但是 JDK 中的线程池的功能,要远比这个强大的多。

3.  JDK 中线程池的使用

JDK 中提供的最核心的线程池工具类 ThreadPoolExecutor,在 JDK 1.8 中这个类最复杂的构造方法有 7 个参数。

ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler)
  • corePoolSize:线程池保有的最小线程数。
  • maximumPoolSize:线程池创建的最大线程数。
  • keepAliveTime:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?很简单,一个线程如果在一段时间内,都没有执行任务,说明很闲,keepAliveTime 和 unit 就是用来定义这个“一段时间”的参数。也就是说,如果一个线程空闲了keepAliveTime & unit这么久,而且线程池的线程数大于 corePoolSize ,那么这个空闲的线程就要被回收了。
  • unit:keepAliveTime 的时间单位
  • workQueue:任务队列
  • threadFactory:线程工厂对象,可以自定义如何创建线程,如给线程指定name。
  • handler:自定义任务的拒绝策略。线程池中所有线程都在忙碌,且任务队列已满,线程池就会拒绝接收再提交的任务。handler 就是拒绝策略,包括 4 种(即RejectedExecutionHandler 接口的 4个实现类)。
    • AbortPolicy:默认的拒绝策略,throws RejectedExecutionException
    • CallerRunsPolicy:提交任务的线程自己去执行该任务
    • DiscardPolicy:直接丢弃任务,不抛出任何异常
    • DiscardOldestPolicy:丢弃最老的任务,加入新的任务

JDK 的并发工具包里还有一个静态线程池工厂类 Executors,可以方便地创建线程池,但是由于 Executors 创建的线程池内部很多地方用到了无界任务队列,在高并发场景下,无界任务队列会接收过多的任务对象,导致 JVM 抛出OutOfMemoryError,整个 JVM 服务崩溃,影响严重。所以很多公司已经不建议使用 Executors 去创建线程。

以上,是Java面试题【什么是线程池】的参考答案。

输出,是最好的学习方法

欢迎在评论区留下你的问题、笔记或知识点补充~

—end—

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧