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

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

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


了解线程池


为什么要使用线程池?


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


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() 方法关闭线程池。


相关文章
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
安全 Java 程序员
面试必看:如何设计一个可以优雅停止的线程?
嘿,大家好!我是小米。今天分享一篇关于“如何停止一个正在运行的线程”的面试干货。通过一次Java面试经历,我明白了停止线程不仅仅是技术问题,更是设计问题。Thread.stop()已被弃用,推荐使用Thread.interrupt()、标志位或ExecutorService来优雅地停止线程,避免资源泄漏和数据不一致。希望这篇文章能帮助你更好地理解Java多线程机制,面试顺利! 我是小米,喜欢分享技术的29岁程序员。欢迎关注我的微信公众号“软件求生”,获取更多技术干货!
378 53
|
数据采集 Java Linux
面试大神教你:如何巧妙回答线程优先级这个经典考题?
大家好,我是小米。本文通过故事讲解Java面试中常见的线程优先级问题。小明和小华的故事帮助理解线程优先级:高优先级线程更可能被调度执行,但并非越高越好。实际开发需权衡业务需求,合理设置优先级。掌握线程优先级不仅能写出高效代码,还能在面试中脱颖而出。最后,小张因深入分析成功拿下Offer。希望这篇文章能助你在面试中游刃有余!
269 4
面试大神教你:如何巧妙回答线程优先级这个经典考题?
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
808 14
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
576 13
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
665 16
|
缓存 安全 Java
面试中的难题:线程异步执行后如何共享数据?
本文通过一个面试故事,详细讲解了Java中线程内部开启异步操作后如何安全地共享数据。介绍了异步操作的基本概念及常见实现方式(如CompletableFuture、ExecutorService),并重点探讨了volatile关键字、CountDownLatch和CompletableFuture等工具在线程间数据共享中的应用,帮助读者理解线程安全和内存可见性问题。通过这些方法,可以有效解决多线程环境下的数据共享挑战,提升编程效率和代码健壮性。
446 6
|
安全 Java 程序员
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
496 11
|
Java Linux 调度
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
490 6
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?

热门文章

最新文章