面试题:用过线程池吗?如何自定义线程池?线程池的参数?

简介: 字节跳动面试题:用过线程池吗?如何自定义线程池?线程池的参数?

面试题:用过线程池吗?如何自定义线程池?线程池的参数?


了解线程池


为什么要使用线程池?


使用线程池可以减少线程的创建和销毁次数,提高程序的性能和效率。它可以管理线程的数量、执行任务队列中的任务,并可配置各种参数以适应不同的应用场景。


Java中的线程池


Java提供了java.util.concurrent包来支持线程池的实现。


使用线程池


使用现有线程池


Java提供了Executors类来创建不同类型的线程池,比如FixedThreadPool、CachedThreadPool等。


示例代码:

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

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池,大小为5
        ExecutorService executor = Executors.newFixedThreadPool(5);

        // 循环提交任务到线程池
        for (int i = 0; i < 10; i++) {
            // 创建一个新的WorkerThread任务,并将其提交到线程池中执行
            Runnable worker = new WorkerThread("Task " + i);
            executor.execute(worker);
        }
        // 关闭线程池,不再接受新的任务
        executor.shutdown();

        // 等待所有线程执行完毕,即线程池中所有任务执行完毕
        while (!executor.isTerminated()) {
        }
        // 所有任务执行完毕后输出提示信息
        System.out.println("所有线程执行完毕");
    }

    // 定义一个实现了Runnable接口的WorkerThread类
    static class WorkerThread implements Runnable {
        private String task; // 任务描述信息

        // 构造函数,用于初始化任务描述信息
        public WorkerThread(String s) {
            this.task = s;
        }

        // 实现Runnable接口的run方法,执行具体的任务逻辑
        public void run() {
            // 输出当前线程名字、任务开始信息
            System.out.println(Thread.currentThread().getName() + " 开始执行任务:" + task);
            // 模拟任务执行,线程休眠2秒
            processCommand();
            // 输出当前线程名字、任务结束信息
            System.out.println(Thread.currentThread().getName() + " 任务执行完毕:" + task);
        }

        // 模拟任务执行的方法
        private void processCommand() {
            try {
                Thread.sleep(2000); // 休眠2秒,模拟任务执行耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


  • 运行结果解释
pool-1-thread-2 开始执行任务:Task 1
pool-1-thread-5 开始执行任务:Task 4
pool-1-thread-4 开始执行任务:Task 3
pool-1-thread-3 开始执行任务:Task 2
pool-1-thread-1 开始执行任务:Task 0
pool-1-thread-2 任务执行完毕:Task 1
pool-1-thread-1 任务执行完毕:Task 0
pool-1-thread-1 开始执行任务:Task 6
pool-1-thread-5 任务执行完毕:Task 4
pool-1-thread-4 任务执行完毕:Task 3
pool-1-thread-4 开始执行任务:Task 8
pool-1-thread-3 任务执行完毕:Task 2
pool-1-thread-3 开始执行任务:Task 9
pool-1-thread-2 开始执行任务:Task 5
pool-1-thread-5 开始执行任务:Task 7
pool-1-thread-2 任务执行完毕:Task 5
pool-1-thread-3 任务执行完毕:Task 9
pool-1-thread-4 任务执行完毕:Task 8
pool-1-thread-5 任务执行完毕:Task 7
pool-1-thread-1 任务执行完毕:Task 6
所有线程执行完毕


1.任务分配和开始执行阶段:

每个任务都被分配给线程池中的一个线程进行执行。

根据输出,我们可以看到五个任务(Task 0 到 Task 4)被分配给了五个不同的线程(pool-1-thread-1 到 pool-1-thread-5)。


2.任务执行阶段:

每个任务开始执行时,输出了一条消息,指示任务开始执行。

任务的执行过程中,每个线程根据调度依次执行其任务。

在这个阶段,线程池中的线程可能会动态地被重新分配给新的任务,以便更有效地利用系统资源。


3.任务执行完毕阶段:

当任务执行完毕时,输出了一条消息,指示任务执行完毕。

这些消息表明每个任务都顺利地完成了执行。


4.所有线程执行完毕阶段:

最后,输出了一条消息,指示所有线程执行完毕。

这表示所有任务都已经被执行完成,并且线程池中的所有线程都处于空闲状态。


自定义线程池


如果现有的线程池类型不能满足需求,可以自定义线程池。自定义线程池可以根据具体需求配置线程的核心数量、最大数量、任务队列等参数。


示例代码:

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CustomThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个自定义线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, // 核心线程数
                4, // 最大线程数
                10, // 空闲线程存活时间
                TimeUnit.SECONDS, // 存活时间单位
                new LinkedBlockingQueue<Runnable>() // 无界任务队列
        );

        // 提交任务到线程池
        for (int i = 0; i < 6; i++) {
            Runnable worker = new WorkerThread("Task " + i, executor);
            executor.execute(worker);
        }

        // 关闭线程池
        executor.shutdown();

        // 等待所有线程执行完毕
        while (!executor.isTerminated()) {
            // 等待
        }

        // 输出所有线程执行完毕
        System.out.println("所有线程执行完毕");
    }

    // 定义一个实现了Runnable接口的WorkerThread类
    static class WorkerThread implements Runnable {
        private String task; // 任务描述信息
        private ThreadPoolExecutor executor; // 线程池

        // 构造函数,用于初始化任务描述信息和线程池
        public WorkerThread(String s, ThreadPoolExecutor executor) {
            this.task = s;
            this.executor = executor;
        }

        // 实现Runnable接口的run方法,执行具体的任务逻辑
        public void run() {
            // 获取当前线程名字
            String threadName = Thread.currentThread().getName();

            // 输出当前线程名字、任务开始信息
            System.out.println("线程 " + threadName + " 开始执行任务:" + task);

            // 获取当前线程池中的核心线程数
            int corePoolSize = executor.getCorePoolSize();

            // 判断当前线程是否为核心线程
            boolean isCoreThread = corePoolSize > 0;

            // 输出是否为核心线程信息
            System.out.println("线程 " + threadName + " 是否为核心线程:" + isCoreThread);

            // 模拟任务执行,线程休眠2秒
            processCommand();

            // 输出当前线程名字、任务结束信息
            System.out.println("线程 " + threadName + " 任务执行完毕:" + task);
        }

        // 模拟任务执行的方法
        private void processCommand() {
            try {
                Thread.sleep(2000); // 休眠2秒,模拟任务执行耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


  • 运行结果
线程 pool-1-thread-2 开始执行任务:Task 1
线程 pool-1-thread-1 开始执行任务:Task 0
线程 pool-1-thread-2 是否为核心线程:true
线程 pool-1-thread-1 是否为核心线程:true
线程 pool-1-thread-1 任务执行完毕:Task 0
线程 pool-1-thread-2 任务执行完毕:Task 1
线程 pool-1-thread-2 开始执行任务:Task 3
线程 pool-1-thread-2 是否为核心线程:true
线程 pool-1-thread-1 开始执行任务:Task 2
线程 pool-1-thread-1 是否为核心线程:true
线程 pool-1-thread-1 任务执行完毕:Task 2
线程 pool-1-thread-1 开始执行任务:Task 4
线程 pool-1-thread-1 是否为核心线程:true
线程 pool-1-thread-2 任务执行完毕:Task 3
线程 pool-1-thread-2 开始执行任务:Task 5
线程 pool-1-thread-2 是否为核心线程:true
线程 pool-1-thread-1 任务执行完毕:Task 4
线程 pool-1-thread-2 任务执行完毕:Task 5
所有线程执行完毕


这个运行结果是一个线程池中的任务执行过程的输出。:


  • 线程 pool-1-thread-2 开始执行任务:Task 1: 线程池中的第二个线程开始执行任务 “Task 1”。
  • 线程 pool-1-thread-1 开始执行任务:Task 0: 线程池中的第一个线程开始执行任务 “Task 0”。
  • 线程 pool-1-thread-2 是否为核心线程:true: 线程池中的第二个线程是一个核心线程。
  • 线程 pool-1-thread-1 是否为核心线程:true: 线程池中的第一个线程是一个核心线程。
  • 线程 pool-1-thread-1 任务执行完毕:Task 0: 线程池中的第一个线程完成了任务 “Task 0”。
  • 线程 pool-1-thread-2 任务执行完毕:Task 1: 线程池中的第二个线程完成了任务 “Task 1”。
  • 线程 pool-1-thread-2 开始执行任务:Task 3: 第二个线程开始执行任务 “Task 3”。
  • 线程 pool-1-thread-2 是否为核心线程:true: 第二个线程仍然是一个核心线程。
  • 线程 pool-1-thread-1 开始执行任务:Task 2: 第一个线程开始执行任务 “Task 2”。
  • 线程 pool-1-thread-1 是否为核心线程:true: 第一个线程仍然是一个核心线程。
  • 线程 pool-1-thread-1 任务执行完毕:Task 2: 第一个线程完成了任务 “Task 2”。
  • 线程 pool-1-thread-1 开始执行任务:Task 4: 第一个线程开始执行任务 “Task 4”。
  • 线程 pool-1-thread-1 是否为核心线程:true: 第一个线程仍然是一个核心线程。
  • 线程 pool-1-thread-2 任务执行完毕:Task 3: 第二个线程完成了任务 “Task 3”。
  • 线程 pool-1-thread-2 开始执行任务:Task 5: 第二个线程开始执行任务 “Task 5”。
  • 线程 pool-1-thread-2 是否为核心线程:true: 第二个线程仍然是一个核心线程。
  • 线程 pool-1-thread-1 任务执行完毕:Task 4: 第一个线程完成了任务 “Task 4”。
  • 线程 pool-1-thread-2 任务执行完毕:Task 5: 第二个线程完成了任务 “Task 5”。
  • 所有线程执行完毕: 所有任务都被线程池中的线程执行完毕了。


自定义线程池参数详解


1.核心线程数(Core Pool Size):


核心线程数是线程池中始终存活的线程数量。

它负责执行任务队列中的任务,即使线程处于空闲状态,也不会被销毁。

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

public class CoreThreadPoolExample {
    public static void main(String[] args) {
        // 设置核心线程数为2
        int corePoolSize = 2;

        // 创建一个线程池,指定核心线程数
        ExecutorService executor = Executors.newFixedThreadPool(corePoolSize);

        // 提交任务到线程池
        for (int i = 0; i < 5; i++) {
            Runnable worker = new WorkerThread("Task " + i);
            executor.execute(worker);
        }

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

    // 定义一个实现了Runnable接口的WorkerThread类
    static class WorkerThread implements Runnable {
        private String task; // 任务描述信息

        // 构造函数,用于初始化任务描述信息
        public WorkerThread(String s) {
            this.task = s;
        }

        // 实现Runnable接口的run方法,执行具体的任务逻辑
        public void run() {
            // 获取当前线程名字
            String threadName = Thread.currentThread().getName();

            // 输出当前线程名字、任务开始信息
            System.out.println("线程 " + threadName + " 开始执行任务:" + task);

            // 模拟任务执行,线程休眠2秒
            processCommand();

            // 输出当前线程名字、任务结束信息
            System.out.println("线程 " + threadName + " 任务执行完毕:" + task);
        }

        // 模拟任务执行的方法
        private void processCommand() {
            try {
                Thread.sleep(2000); // 休眠2秒,模拟任务执行耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  • 运行结果
线程 pool-1-thread-1 开始执行任务:Task 0
线程 pool-1-thread-2 开始执行任务:Task 1
线程 pool-1-thread-2 任务执行完毕:Task 1
线程 pool-1-thread-1 任务执行完毕:Task 0
线程 pool-1-thread-2 开始执行任务:Task 2
线程 pool-1-thread-1 开始执行任务:Task 3
线程 pool-1-thread-2 任务执行完毕:Task 2
线程 pool-1-thread-1 任务执行完毕:Task 3
线程 pool-1-thread-2 开始执行任务:Task 4
线程 pool-1-thread-2 任务执行完毕:Task 4


  1. 最大线程数(Maximum Pool Size):
  • 最大线程数是线程池中允许的最大线程数量。
  • 当任务队列已满且当前线程数小于最大线程数时,线程池会创建新的线程来执行任务。
import java.util.concurrent.*;

public class CustomThreadPoolExample {
    public static void main(String[] args) {
        int corePoolSize = 5; // 设置核心线程数
        int maxPoolSize = 10; // 设置最大线程数
        long keepAliveTime = 60; // 设置线程空闲时间
        TimeUnit unit = TimeUnit.SECONDS; // 设置空闲时间单位
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100); // 设置任务队列

        // 创建线程池
        ExecutorService executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);

        // 提交任务到线程池
        for (int i = 0; i < 15; i++) {
            Runnable worker = new WorkerThread("Task " + i);
            executor.execute(worker);
        }

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

    // 定义一个实现了Runnable接口的WorkerThread类
    static class WorkerThread implements Runnable {
        private String task; // 任务描述信息

        // 构造函数,用于初始化任务描述信息
        public WorkerThread(String s) {
            this.task = s;
        }

        // 实现Runnable接口的run方法,执行具体的任务逻辑
        public void run() {
            // 获取当前线程名字
            String threadName = Thread.currentThread().getName();

            // 输出当前线程名字、任务开始信息
            System.out.println("线程 " + threadName + " 开始执行任务:" + task);

            // 模拟任务执行,线程休眠2秒
            processCommand();

            // 输出当前线程名字、任务结束信息
            System.out.println("线程 " + threadName + " 任务执行完毕:" + task);
        }

        // 模拟任务执行的方法
        private void processCommand() {
            try {
                Thread.sleep(2000); // 休眠2秒,模拟任务执行耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


  1. 空闲线程存活时间(Keep Alive Time):
  • 当线程池中的线程数量超过核心线程数时,空闲线程等待新任务的最长时间。
  • 超过这个时间后,空闲线程将被销毁,以节省系统资源。
import java.util.concurrent.*;

public class KeepAliveTimeExample {
    public static void main(String[] args) {
        int corePoolSize = 3; // 设置核心线程数
        int maxPoolSize = 5; // 设置最大线程数
        long keepAliveTime = 3; // 设置空闲线程存活时间(秒)
        TimeUnit unit = TimeUnit.SECONDS; // 设置空闲时间单位
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10); // 设置任务队列

        // 创建线程池,并设置空闲线程存活时间
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);

        // 提交任务到线程池
        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread("Task " + i);
            executor.execute(worker);
        }

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

    // 定义一个实现了Runnable接口的WorkerThread类
    static class WorkerThread implements Runnable {
        private String task; // 任务描述信息

        // 构造函数,用于初始化任务描述信息
        public WorkerThread(String s) {
            this.task = s;
        }

        // 实现Runnable接口的run方法,执行具体的任务逻辑
        public void run() {
            // 获取当前线程名字
            String threadName = Thread.currentThread().getName();

            // 输出当前线程名字、任务开始信息
            System.out.println("线程 " + threadName + " 开始执行任务:" + task);

            // 模拟任务执行,线程休眠2秒
            processCommand();

            // 输出当前线程名字、任务结束信息
            System.out.println("线程 " + threadName + " 任务执行完毕:" + task);
        }

        // 模拟任务执行的方法
        private void processCommand() {
            try {
                Thread.sleep(2000); // 休眠2秒,模拟任务执行耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


  1. 任务队列(Work Queue):
  • 任务队列用于保存等待执行的任务。
  • 不同类型的任务队列(如ArrayBlockingQueue、LinkedBlockingQueue等)具有不同的特性,如有界性、无界性等。
  • ArrayBlockingQueue:有界队列,容量为10,当任务队列已满时,新的任务无法加入队列,需要等待队列中的任务被处理。
  • LinkedBlockingQueue:无界队列,可以无限制地添加任务,不会出现队列满的情况,但在高负载情况下可能会导致内存溢出。
import java.util.concurrent.*;

public class WorkQueueExample {
    public static void main(String[] args) {
        int corePoolSize = 3; // 设置核心线程数
        int maxPoolSize = 5; // 设置最大线程数
        long keepAliveTime = 3; // 设置空闲线程存活时间(秒)
        TimeUnit unit = TimeUnit.SECONDS; // 设置空闲时间单位

        // 使用有界队列(ArrayBlockingQueue)
        BlockingQueue<Runnable> arrayBlockingQueue = new ArrayBlockingQueue<>(10);
        ThreadPoolExecutor executorWithArrayBlockingQueue = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, arrayBlockingQueue);

        // 使用无界队列(LinkedBlockingQueue)
        BlockingQueue<Runnable> linkedBlockingQueue = new LinkedBlockingQueue<>();
        ThreadPoolExecutor executorWithLinkedBlockingQueue = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, linkedBlockingQueue);

        // 提交任务到线程池(使用有界队列)
        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread("Task " + i);
            executorWithArrayBlockingQueue.execute(worker);
        }

        // 提交任务到线程池(使用无界队列)
        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread("Task " + i);
            executorWithLinkedBlockingQueue.execute(worker);
        }

        // 关闭线程池(使用有界队列)
        executorWithArrayBlockingQueue.shutdown();

        // 关闭线程池(使用无界队列)
        executorWithLinkedBlockingQueue.shutdown();
    }

    // 定义一个实现了Runnable接口的WorkerThread类
    static class WorkerThread implements Runnable {
        private String task; // 任务描述信息

        // 构造函数,用于初始化任务描述信息
        public WorkerThread(String s) {
            this.task = s;
        }

        // 实现Runnable接口的run方法,执行具体的任务逻辑
        public void run() {
            // 获取当前线程名字
            String threadName = Thread.currentThread().getName();

            // 输出当前线程名字、任务开始信息
            System.out.println("线程 " + threadName + " 开始执行任务:" + task);

            // 模拟任务执行,线程休眠2秒
            processCommand();

            // 输出当前线程名字、任务结束信息
            System.out.println("线程 " + threadName + " 任务执行完毕:" + task);
        }

        // 模拟任务执行的方法
        private void processCommand() {
            try {
                Thread.sleep(2000); // 休眠2秒,模拟任务执行耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  1. 拒绝策略(Rejected Execution Handler):
  • 当任务队列已满且线程池中的线程数量已达到最大线程数时,线程池无法执行新的任务时的处理策略。
  • 拒绝策略可以是抛出异常、丢弃任务、阻塞等。
  • AbortPolicy:默认的拒绝策略,当任务无法被执行时,会抛出 RejectedExecutionException 异常。

DiscardPolicy:丢弃策略,当任务无法被执行时,会默默地丢弃掉这个任务。

DiscardOldestPolicy:丢弃最旧策略,当任务无法被执行时,会移除最早进入任务队列的任务,然后尝试再次提交当前任务。

CallerRunsPolicy:调用者运行策略,当任务无法被执行时,会由提交任务的线程执行这个任务。

import java.util.concurrent.*;

public class RejectedExecutionHandlerExample {
    public static void main(String[] args) {
        int corePoolSize = 2; // 设置核心线程数
        int maxPoolSize = 3; // 设置最大线程数
        long keepAliveTime = 3; // 设置空闲线程存活时间(秒)
        TimeUnit unit = TimeUnit.SECONDS; // 设置空闲时间单位
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2); // 设置任务队列(有界队列)

        // 创建线程池,并设置空闲线程存活时间和拒绝策略
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);

        // 设置四种不同的拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); // 中止策略
        //executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); // 丢弃策略
        //executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); // 丢弃最旧策略
        //executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 调用者运行策略

        // 提交任务到线程池
        for (int i = 0; i < 5; i++) {
            Runnable worker = new WorkerThread("Task " + i);
            executor.execute(worker);
        }

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

    // 定义一个实现了Runnable接口的WorkerThread类
    static class WorkerThread implements Runnable {
        private String task; // 任务描述信息

        // 构造函数,用于初始化任务描述信息
        public WorkerThread(String s) {
            this.task = s;
        }

        // 实现Runnable接口的run方法,执行具体的任务逻辑
        public void run() {
            // 获取当前线程名字
            String threadName = Thread.currentThread().getName();

            // 输出当前线程名字、任务开始信息
            System.out.println("线程 " + threadName + " 开始执行任务:" + task);

            // 模拟任务执行,线程休眠2秒
            processCommand();

            // 输出当前线程名字、任务结束信息
            System.out.println("线程 " + threadName + " 任务执行完毕:" + task);
        }

        // 模拟任务执行的方法
        private void processCommand() {
            try {
                Thread.sleep(2000); // 休眠2秒,模拟任务执行耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


线程池的应用场景


1. Web服务器


  • 在Web服务器中,经常需要处理大量的客户端请求。
  • 使用线程池可以有效地管理服务器资源,提高并发处理能力,保证服务器的稳定性和性能。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class WebServer {
    private static final int MAX_THREADS = 100; // 最大线程数

    public static void main(String[] args) {
        // 创建一个固定大小的线程池,用于处理客户端请求
        ExecutorService executor = Executors.newFixedThreadPool(MAX_THREADS);

        // 模拟客户端请求
        for (int i = 0; i < 20; i++) {
            // 创建客户端请求任务并提交给线程池执行
            executor.execute(new ClientRequest("Request " + i));
        }

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

    // 定义一个模拟客户端请求的任务类
    static class ClientRequest implements Runnable {
        private String request;

        // 构造函数,初始化客户端请求信息
        public ClientRequest(String request) {
            this.request = request;
        }

        @Override
        public void run() {
            // 模拟处理客户端请求的业务逻辑
            processRequest();
        }

        // 模拟处理客户端请求的业务逻辑
        private void processRequest() {
            // 输出开始处理请求的信息
            System.out.println("开始处理客户端请求:" + request);

            try {
                // 模拟处理请求的耗时操作
                Thread.sleep(1000);

                // 输出请求处理完成的信息
                System.out.println("请求处理完成:" + request);
            } catch (InterruptedException e) {
                // 输出请求处理被中断的信息
                System.out.println("请求处理被中断:" + request);
            }
        }
    }
}

开始处理客户端请求:Request 0
开始处理客户端请求:Request 2
开始处理客户端请求:Request 1
开始处理客户端请求:Request 3
开始处理客户端请求:Request 4
开始处理客户端请求:Request 5
开始处理客户端请求:Request 6
开始处理客户端请求:Request 7
开始处理客户端请求:Request 8
开始处理客户端请求:Request 9
开始处理客户端请求:Request 10
开始处理客户端请求:Request 12
开始处理客户端请求:Request 11
开始处理客户端请求:Request 13
开始处理客户端请求:Request 14
开始处理客户端请求:Request 15
开始处理客户端请求:Request 16
开始处理客户端请求:Request 18
开始处理客户端请求:Request 19
开始处理客户端请求:Request 17
请求处理完成:Request 13
请求处理完成:Request 0
请求处理完成:Request 8
请求处理完成:Request 7
请求处理完成:Request 16
请求处理完成:Request 6
请求处理完成:Request 14
请求处理完成:Request 5
请求处理完成:Request 3
请求处理完成:Request 15
请求处理完成:Request 4
请求处理完成:Request 19
请求处理完成:Request 2
请求处理完成:Request 1
请求处理完成:Request 12
请求处理完成:Request 11
请求处理完成:Request 10
请求处理完成:Request 9
请求处理完成:Request 18
请求处理完成:Request 17

2. 数据库连接池


  • 数据库连接是一种宝贵的资源,频繁地创建和销毁连接会导致系统性能下降。
  • 使用线程池管理数据库连接可以避免资源的频繁分配和释放,提高数据库访问的效率。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DatabaseConnectionPool {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/mydatabase";
    private static final String DB_USER = "username";
    private static final String DB_PASSWORD = "password";

    private static final int POOL_SIZE = 10; // 连接池大小

    private BlockingQueue<Connection> connectionPool; // 连接池
    private ExecutorService executorService; // 线程池

    public DatabaseConnectionPool() {
        // 初始化连接池
        connectionPool = new ArrayBlockingQueue<>(POOL_SIZE);
        // 初始化线程池
        executorService = Executors.newCachedThreadPool();

        // 初始化连接池中的连接
        for (int i = 0; i < POOL_SIZE; i++) {
            try {
                Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
                connectionPool.put(connection);
            } catch (SQLException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 从连接池中获取连接
    public Connection getConnection() throws InterruptedException {
        return connectionPool.take();
    }

    // 将连接放回连接池
    public void releaseConnection(Connection connection) throws InterruptedException {
        if (connection != null) {
            connectionPool.put(connection);
        }
    }

    // 关闭连接池
    public void closePool() {
        if (executorService != null && !executorService.isShutdown()) {
            executorService.shutdown();
        }
        connectionPool.forEach(connection -> {
            try {
                if (connection != null && !connection.isClosed()) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        });
    }
}

在这个数据库连接池的示例中,采用了基于阻塞队列的方式来实现连接池的管理,以及使用线程池来处理数据库连接的请求。


  • 连接池的初始化: 在构造函数中,创建了一个固定大小的阻塞队列作为连接池,并使用 JDBC 连接来填充这个连接池。
  • 获取连接和释放连接: getConnection() 方法用于从连接池中获取连接,releaseConnection() 方法用于将连接放回连接池。
  • 关闭连接池: closePool() 方法用于关闭连接池,在关闭之前会确保连接池中的所有连接都被正确地关闭。


3. 图像处理


  • 在图像处理应用中,通常需要处理大量的图片文件。
  • 使用线程池可以并发地处理多张图片,加快处理速度,提高用户体验。
import java.io.File;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ImageProcessor {
    private static final String IMAGE_DIRECTORY = "/path/to/image/directory";
    private static final int THREAD_POOL_SIZE = 5;

    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);

        // 获取图像文件列表
        File imageDirectory = new File(IMAGE_DIRECTORY);
        File[] imageFiles = imageDirectory.listFiles();

        // 处理每张图片
        if (imageFiles != null) {
            for (File imageFile : imageFiles) {
                // 提交任务给线程池处理
                executor.submit(new ImageTask(imageFile));
            }
        }

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

    // 图像处理任务类
    static class ImageTask implements Runnable {
        private File imageFile;

        public ImageTask(File imageFile) {
            this.imageFile = imageFile;
        }

        @Override
        public void run() {
            // 执行图像处理逻辑
            processImage(imageFile);
        }

        // 图像处理逻辑
        private void processImage(File imageFile) {
            System.out.println("处理图像文件:" + imageFile.getName());
            // 在这里编写具体的图像处理逻辑
            // 例如,缩放、裁剪、滤镜等操作
        }
    }
}

这个示例演示了如何使用线程池处理图像文件。主要步骤包括:


  1. 创建线程池: 使用 Executors.newFixedThreadPool() 方法创建一个固定大小的线程池,指定了线程池的大小为 5。
  2. 获取图像文件列表: 从指定的图像目录中获取图像文件列表。
  3. 提交任务给线程池: 遍历图像文件列表,为每个图像文件创建一个 ImageTask 任务,并将其提交给线程池执行。
  4. 图像处理逻辑: ImageTask 类实现了 Runnable 接口,其中的 run() 方法定义了图像处理的逻辑。在 processImage() 方法中,可以编写具体的图像处理逻辑,例如图像缩放、裁剪、添加滤镜等操作。
  5. 关闭线程池: 在任务执行完毕后,调用线程池的 shutdown() 方法关闭线程池。


4. 文件上传下载


  • 文件上传下载是Web应用中常见的功能之一。
  • 使用线程池可以并发处理多个上传下载任务,提高文件传输效率,缩短用户等待时间。
import java.io.File;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FileTransferManager {
    private static final String UPLOAD_DIRECTORY = "/path/to/upload/directory";
    private static final int THREAD_POOL_SIZE = 5;

    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);

        // 获取上传目录中的文件列表
        File uploadDirectory = new File(UPLOAD_DIRECTORY);
        File[] files = uploadDirectory.listFiles();

        // 处理每个文件(模拟上传下载任务)
        if (files != null) {
            for (File file : files) {
                // 提交上传下载任务给线程池处理
                executor.submit(new FileTransferTask(file));
            }
        }

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

    // 文件传输任务类
    static class FileTransferTask implements Runnable {
        private File file;

        public FileTransferTask(File file) {
            this.file = file;
        }

        @Override
        public void run() {
            // 执行文件上传下载逻辑
            transferFile(file);
        }

        // 文件上传下载逻辑
        private void transferFile(File file) {
            System.out.println("处理文件:" + file.getName());
            // 在这里编写具体的文件上传下载逻辑
            // 例如,将文件上传到远程服务器,或者从远程服务器下载文件等操作
        }
    }
}

这个示例展示了如何使用线程池处理文件上传下载任务。主要步骤如下:


  1. 创建线程池: 使用 Executors.newFixedThreadPool() 方法创建一个固定大小的线程池,指定了线程池的大小为 5。
  2. 获取上传目录中的文件列表: 从指定的上传目录中获取文件列表。
  3. 提交任务给线程池: 遍历文件列表,为每个文件创建一个 FileTransferTask 任务,并将其提交给线程池执行。
  4. 文件上传下载逻辑: FileTransferTask 类实现了 Runnable 接口,其中的 run() 方法定义了文件上传下载的逻辑。在 transferFile() 方法中,可以编写具体的文件上传下载逻辑,例如将文件上传到远程服务器,或者从远程服务器下载文件等操作。
  5. 关闭线程池: 在任务执行完毕后,调用线程池的 shutdown() 方法关闭线程池。


相关文章
|
1月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
155 0
|
1月前
|
存储
经典面试题:写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个 复制 #define MIN(a,b) ((a)<=(b)?(a):(b))
你的宏定义已非常接近标准。以下是改进后的 `MIN` 宏定义,支持多种数据类型并避免副作用:
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
75 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
2月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
51 2
面试官:说说停止线程池的执行流程?
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:如何实现线程池任务编排?
面试官:如何实现线程池任务编排?
33 1
面试官:如何实现线程池任务编排?
|
1月前
|
设计模式 Java 物联网
【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解
【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解
58 0
|
3月前
|
存储 Java 开发者
HashMap线程安全问题大揭秘:ConcurrentHashMap、自定义同步,一文让你彻底解锁!
【8月更文挑战第24天】HashMap是Java集合框架中不可或缺的一部分,以其高效的键值对存储和快速访问能力广受开发者欢迎。本文深入探讨了HashMap在JDK 1.8后的底层结构——数组+链表+红黑树混合模式,这种设计既利用了数组的快速定位优势,又通过链表和红黑树有效解决了哈希冲突问题。数组作为基石,每个元素包含一个Node节点,通过next指针形成链表;当链表长度过长时,采用红黑树进行优化,显著提升性能。此外,还介绍了HashMap的扩容机制,确保即使在数据量增大时也能保持高效运作。通过示例代码展示如何使用HashMap进行基本操作,帮助理解其实现原理及应用场景。
55 1
|
3月前
|
消息中间件 缓存 算法
Java多线程面试题总结(上)
进程和线程是操作系统管理程序执行的基本单位,二者有明显区别: 1. **定义与基本单位**:进程是资源分配的基本单位,拥有独立的内存空间;线程是调度和执行的基本单位,共享所属进程的资源。 2. **独立性与资源共享**:进程间相互独立,通信需显式机制;线程共享进程资源,通信更直接快捷。 3. **管理与调度**:进程管理复杂,线程管理更灵活。 4. **并发与并行**:进程并发执行,提高资源利用率;线程不仅并发还能并行执行,提升执行效率。 5. **健壮性**:进程更健壮,一个进程崩溃不影响其他进程;线程崩溃可能导致整个进程崩溃。
48 2
|
3月前
|
存储 缓存 安全
Java多线程面试题总结(中)
Java内存模型(JMM)定义了程序中所有变量的访问规则与范围,确保多线程环境下的数据一致性。JMM包含主内存与工作内存的概念,通过8种操作管理两者间的交互,确保原子性、可见性和有序性。`synchronized`和`volatile`关键字提供同步机制,前者确保互斥访问,后者保证变量更新的可见性。多线程操作涉及不同状态,如新建(NEW)、可运行(RUNNABLE)等,并可通过中断、等待和通知等机制协调线程活动。`volatile`虽不确保线程安全,但能确保变量更新对所有线程可见。
19 0