Skip to content

线程池

发表于: 时间 15:07
加载中...

线程池主要核心原理

线程池的创建

线程池的创建,主要使用 Executors 工具类中的两个方法,一个是 newCachedThreadPool,一个是 newFixedThreadPool,前者是用来创建一个无长度上限的线程池,后者是用来创建一个有长度上限的线程池,他们都会返回一个 ExecutorService 对象,具体如下:

ExecutorService e1 = Executors.newCachedThreadPool(); //创建一个无上限的线程池
ExecutorService e2 = Executors.newFixedThreadPool(3); //创建一个最多可以容纳5个线程的线程池

向线程池中提交任务

接下来我们测试线程池是否真的能达到像它所说的那样的效果,提交任务常使用的方法是 submitexecute 方法,通常通过 execute(Runnable) 提交任务仅执行而不返回结果,而 submit(Runnable) 会返回 Future,可用于获取执行状态或异常信息。

我们做如下测试:

public class Main {
    public static void main(String[] args){
        //创建线程
        ExecutorService e1 = Executors.newCachedThreadPool(); //创建一个无上限的线程池
        //ExecutorService e2 = Executors.newFixedThreadPool(5); //创建一个最多可以容纳5个线程的线程池
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 在运行。");
            }
        };
        e1.submit(r);
    }
}

image.png

如上,线程已经在执行了,可以发现程序并没有结束,因此在后面我们要在线程池不需要使用的时候进行销毁,但一般线程池是不需要关闭的。

我们接下来多测试几个线程:

public class Main {
    public static void main(String[] args){
        //创建线程
        ExecutorService e1 = Executors.newCachedThreadPool(); //创建一个无上限的线程池
        //ExecutorService e2 = Executors.newFixedThreadPool(5); //创建一个最多可以容纳5个线程的线程池
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 在运行。");
            }
        };
        e1.submit(r);
        e1.submit(r);
        e1.submit(r);
        e1.submit(r);
        e1.submit(r);
    }
}

image.png

如图,我们添加了 5 个任务。为了进一步测试,我们在添加人物之前进行一小段时间的睡眠,验证已经执行完的线程池里的线程对象能不能得到复用,如下:

package top.mygld.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        //创建线程
        ExecutorService e1 = Executors.newCachedThreadPool(); //创建一个无上限的线程池
        //ExecutorService e2 = Executors.newFixedThreadPool(5); //创建一个最多可以容纳5个线程的线程池
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 在运行。");
            }
        };
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
    }
}

image.png

如上,线程 1 执行完毕后,被多次复用。

接下来再测试一下 newFixedThreadPool:

package top.mygld.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        //创建线程
        //ExecutorService e1 = Executors.newCachedThreadPool(); //创建一个无上限的线程池
        ExecutorService e2 = Executors.newFixedThreadPool(3); //创建一个最多可以容纳5个线程的线程池
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 在运行。");
            }
        };
        e2.submit(r);
        e2.submit(r);
        e2.submit(r);
        e2.submit(r);
        e2.submit(r);
    }
}

image.png

非常完美,如上图,线程最多只能被使用 3 个。

线程池的销毁

已经没有用的线程池,只需要使用 shutdown 方法销毁即可,这里不再演示。


自定义线程池

除了使用 Executors 提供的工厂方法,我们也可以通过 ThreadPoolExecutor 来创建一个更加灵活、可控的线程池。它的构造方法如下:

public ThreadPoolExecutor(
        int corePoolSize,        // 核心线程数
        int maximumPoolSize,     // 最大线程数
        long keepAliveTime,      // 非核心线程的最大存活时间
        TimeUnit unit,           // 存活时间的单位
        BlockingQueue<Runnable> workQueue, // 任务队列
        ThreadFactory threadFactory,       // 线程工厂,用于创建新线程
        RejectedExecutionHandler handler   // 拒绝策略
)

参数含义

  1. corePoolSize 核心线程数,线程池会始终保持的线程数量,即使这些线程处于空闲状态。
  2. maximumPoolSize 最大线程数,当任务过多时,线程池最多能扩展到的线程数量。
  3. keepAliveTime & unit 非核心线程(超过 corePoolSize 的部分)在空闲时的存活时间,超时后会被回收。
  4. workQueue 存放等待执行任务的阻塞队列。常见选择:
    • ArrayBlockingQueue(有界队列,数组实现)
    • LinkedBlockingQueue(无界队列,链表实现)
    • SynchronousQueue(直接提交任务,不存储)
  5. threadFactory 定义线程的创建方式,比如设置线程名、是否为守护线程等。默认工厂即可。
  6. handler 当线程池和队列都满了时的拒绝策略:
    • AbortPolicy(默认,抛异常)
    • CallerRunsPolicy(由调用者线程执行任务)
    • DiscardPolicy(直接丢弃任务)
    • DiscardOldestPolicy(丢弃队列中最老的任务)

示例:自定义线程池

package top.mygld.demo;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        // 自定义线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,                      // 核心线程数
                5,                      // 最大线程数
                10, TimeUnit.SECONDS,   // 非核心线程空闲存活时间
                new ArrayBlockingQueue<>(3),  // 有界任务队列,最多存 3 个任务
                Executors.defaultThreadFactory(), // 默认线程工厂
                new ThreadPoolExecutor.AbortPolicy() // 拒绝策略:直接抛异常
        );

        Runnable task = () -> {
            System.out.println(Thread.currentThread().getName() + " 在执行任务");
            try {
                Thread.sleep(2000); // 模拟耗时任务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        // 提交 10 个任务,观察线程池如何调度
        for (int i = 1; i <= 10; i++) {
            try {
                pool.execute(task);
            } catch (RejectedExecutionException e) {
                System.out.println("任务 " + i + " 被拒绝了!");
            }
        }

        pool.shutdown(); // 关闭线程池
    }
}

c11effa6-8ac4-4dab-acd1-bf8f0085253c.png

执行流程说明

最大并行数

最大并行数的含义

获取最大并行数可以通过下述代码获取:

public class Main {
    public static void main(String[] args) {
        int i = Runtime.getRuntime().availableProcessors();
        System.out.println(i);
    }
}

线程池多大合适

CPU 密集型运算

最大并行数 + 1

I/O 密集型运算

最大并行数期望CPU利用率总时间(CPU计算时间+等待时间)CPU计算时间最大并行数 * 期望 CPU 利用率 * \frac{总时间(CPU计算时间+等待时间)}{CPU计算时间}

面试八股文重点

这些内容是面试经常会提问的点,后期再做整理。


上一篇文章
实词与成语积累
下一篇文章
Java 的动态代理