2.Java面试题之线程池

1. 写在前面

线程池是一种管理线程的机制,通过预先创建一定数量的线程,可以在需要时重复使用这些线程,从而避免频繁创建和销毁线程带来的性能开销。

使用线程池的优点包括:

  • 提高性能:线程池减少了线程创建和销毁的开销。
  • 资源管理:线程池控制了线程的数量,避免系统资源耗尽。
  • 任务管理:线程池提供了任务队列,可以管理和调度任务的执行。
  • 简化编程:线程池提供了一些高级功能,如定时任务、周期性任务等,简化了并发编程。

2. Java 中如何创建线程池?

Java 提供了 java.util.concurrent 包中的 Executors 工具类来创建各种类型的线程池。常见的方法包括:

2.1 固定大小线程池

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

2.2 缓存线程池

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

2.3 单线程池

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

2.4 定时线程池

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

3. 如何提交任务到线程池?

可以通过 execute 方法或 submit 方法将任务提交到线程池。

3.1 使用 execute 方法

fixedThreadPool.execute(new Runnable() {
    @Override
    public void run() {
        System.out.println("Task executed");
    }
});

3.2 使用 submit 方法

Future<?> future = fixedThreadPool.submit(new Callable<String>() {
    @Override
    public String call() throws Exception {
        return "Task completed";
    }
});

try {
    String result = future.get();
    System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

4. 什么是 Future 和 Callable?

Callable 是一个类似于 Runnable 的接口,但它可以返回一个结果或抛出异常。Future 表示一个异步计算的结果,可以用来获取 Callable 的返回值或检查任务是否完成。

Callable<String> callableTask = new Callable<String>() {
    @Override
    public String call() throws Exception {
        return "Task result";
    }
};

Future<String> future = fixedThreadPool.submit(callableTask);

try {
    String result = future.get();  // 阻塞等待任务完成并获取结果
    System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

5. 如何优雅地关闭线程池?

可以使用 shutdown 或 shutdownNow 方法来关闭线程池。

5.1 shutdown 方法

停止接受新任务,并让已提交的任务执行完毕。

fixedThreadPool.shutdown();

5.2 shutdownNow 方法

尝试停止所有正在执行的任务,并返回等待执行的任务列表。

List<Runnable> notExecutedTasks = fixedThreadPool.shutdownNow();

5.3 等待线程池关闭

try {
    if (!fixedThreadPool.awaitTermination(60, TimeUnit.SECONDS)) {
        fixedThreadPool.shutdownNow();
    }
} catch (InterruptedException e) {
    fixedThreadPool.shutdownNow();
}

6. 线程池的核心参数有哪些?

ThreadPoolExecutor 是 Java 线程池的核心实现类,它的构造函数包含以下参数:

  • corePoolSize:核心线程数,即线程池中始终保持的线程数量。
  • maximumPoolSize:最大线程数,即线程池中允许的最大线程数量。
  • keepAliveTime:非核心线程的空闲时间,超过这个时间将被终止。
  • unit:空闲时间的时间单位。
  • workQueue:任务队列,用于存储等待执行的任务。
  • threadFactory:线程工厂,用于创建新线程。
  • handler:拒绝策略,当任务无法执行时的处理方式。
    5,  // corePoolSize
    10, // maximumPoolSize
    60, // keepAliveTime
    TimeUnit.SECONDS, // unit
    new LinkedBlockingQueue<Runnable>(), // workQueue
    Executors.defaultThreadFactory(), // threadFactory
    new ThreadPoolExecutor.AbortPolicy() // handler
);

7. 线程池的拒绝策略有哪些?

当线程池无法接受新任务时,会使用拒绝策略来处理这些任务。ThreadPoolExecutor 提供了以下几种拒绝策略:

7.1 AbortPolicy

默认策略,抛出 RejectedExecutionException

new ThreadPoolExecutor.AbortPolicy();

7.2 CallerRunsPolicy

由调用线程执行任务

new ThreadPoolExecutor.CallerRunsPolicy();

7.3 DiscardPolicy

直接丢弃任务,不抛出异常。

new ThreadPoolExecutor.DiscardPolicy();

7.4 DiscardOldestPolicy

丢弃队列中最旧的任务,然后尝试重新提交任务。

new ThreadPoolExecutor.DiscardOldestPolicy();

8. 什么是 ForkJoinPool?它与 ThreadPoolExecutor 有什么区别?

ForkJoinPool 是 Java 7 引入的一种特殊的线程池,设计用于处理可以递归拆分成更小任务的并行计算。它基于工作窃取算法,适合处理大规模并行任务。
ForkJoinPool 与 ThreadPoolExecutor 的主要区别在于:

  • 任务类型:ForkJoinPool 适用于可以分解的任务,而 ThreadPoolExecutor 适用于独立的任务。
  • 工作窃取:ForkJoinPool 使用工作窃取算法,提高了多核 CPU 的利用率,而 ThreadPoolExecutor 使用固定的任务队列。
  • API:ForkJoinPool 使用 ForkJoinTask(包括 RecursiveTask 和 RecursiveAction)来表示任务,而 ThreadPoolExecutor 使用 Runnable 和 Callable。
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

class SumTask extends RecursiveTask<Integer> {
    private final int[] array;
    private final int start, end;

    public SumTask(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if (end - start <= 10) {
            int sum = 0;
            for (int i = start; i < end; i++) {
                sum += array[i];
            }
            return sum;
        } else {
            int mid = (start + end) / 2;
            SumTask leftTask = new SumTask(array, start, mid);
            SumTask rightTask = new SumTask(array, mid, end);
            leftTask.fork();
            return rightTask.compute() + leftTask.join();
        }
    }
}

public class ForkJoinExample {
    public static void main(String[] args) {
        int[] array = new int[100];
        for (int i = 0; i < array.length; i++) {
            array[i] = i + 1;
        }

        ForkJoinPool pool = new ForkJoinPool();
        SumTask task = new SumTask(array, 0, array.length);
        int result = pool.invoke(task);
        System.out.println("Sum: " + result);
    }
}

9. 如何自定义线程池的拒绝策略?

在 ThreadPoolExecutor 中,当线程池和队列都满时,可以自定义拒绝策略来处理新提交的任务。可以通过实现 RejectedExecutionHandler 接口来自定义拒绝策略。

import java.util.concurrent.*;

public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 自定义拒绝策略,例如记录日志或将任务放入另一个队列
        System.out.println("Task " + r.toString() + " rejected from " + executor.toString());
    }

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2),
            Executors.defaultThreadFactory(),
            new CustomRejectedExecutionHandler()
        );

        for (int i = 0; i < 10; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is executing task");
            });
        }

        executor.shutdown();
    }
}

10. 如何实现一个自定义的线程池?

可以通过继承 ThreadPoolExecutor 类来实现一个自定义的线程池,并重写其方法以添加自定义行为。

import java.util.concurrent.*;

public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
    public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        System.out.println("Before executing task: " + r.toString());
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        System.out.println("After executing task: " + r.toString());
    }

    @Override
    protected void terminated() {
        super.terminated();
        System.out.println("Thread pool terminated");
    }

    public static void main(String[] args) {
        CustomThreadPoolExecutor executor = new CustomThreadPoolExecutor(
            2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2)
        );

        for (int i = 0; i < 10; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is executing task");
            });
        }

        executor.shutdown();
    }
}

11. 如何实现一个具有动态调整功能的线程池?

可以通过定期调整 ThreadPoolExecutor 的核心参数来实现动态调整线程池的功能。例如,可以使用 ScheduledExecutorService 定期检查任务队列的长度,并根据需要调整核心线程数和最大线程数。

import java.util.concurrent.*;

public class DynamicThreadPool {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2)
        );

        ScheduledExecutorService adjuster = Executors.newScheduledThreadPool(1);
        adjuster.scheduleAtFixedRate(() -> {
            int queueSize = executor.getQueue().size();
            if (queueSize > 2) {
                executor.setCorePoolSize(Math.min(executor.getCorePoolSize() + 1, 10));
                executor.setMaximumPoolSize(Math.min(executor.getMaximumPoolSize() + 1, 20));
            } else if (queueSize == 0) {
                executor.setCorePoolSize(Math.max(executor.getCorePoolSize() - 1, 2));
                executor.setMaximumPoolSize(Math.max(executor.getMaximumPoolSize() - 1, 4));
            }
            System.out.println("Adjusted Pool Size: " + executor.getCorePoolSize() + ", " + executor.getMaximumPoolSize());
        }, 0, 1, TimeUnit.SECONDS);

        for (int i = 0; i < 10; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is executing task");
            });
        }

        executor.shutdown();
        try {
            executor.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        adjuster.shutdown();
    }
}

12. 如何实现一个线程池的优先级队列?

可以使用 PriorityBlockingQueue 来实现一个具有优先级的线程池。任务需要实现 Comparable 接口,以定义任务的优先级。

import java.util.concurrent.*;

public class PriorityThreadPool {
    static class PriorityTask implements Runnable, Comparable<PriorityTask> {
        private final int priority;
        private final String name;

        public PriorityTask(int priority, String name) {
            this.priority = priority;
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " is executing task: " + name);
        }

        @Override
        public int compareTo(PriorityTask o) {
            return Integer.compare(o.priority, this.priority);
        }

        @Override
        public String toString() {
            return name + "(priority=" + priority + ")";
        }
    }

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, 4, 60, TimeUnit.SECONDS, new PriorityBlockingQueue<>()
        );

        for (int i = 0; i < 10; i++) {
            int priority = i % 3;
            executor.execute(new PriorityTask(priority, "Task-" + i));
        }

        executor.shutdown();
    }
}

13. 如何实现一个支持超时任务的线程池?

可以使用 ScheduledThreadPoolExecutor 来实现一个支持超时任务的线程池。通过 schedule 方法,可以提交一个带有超时功能的任务。

import java.util.concurrent.*;

public class TimeoutThreadPool {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(4);

        for (int i = 0; i < 10; i++) {
            ScheduledFuture<?> future = executor.schedule(() -> {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is executing task");
            }, 1, TimeUnit.SECONDS);

            executor.schedule(() -> {
                if (!future.isDone()) {
                    future.cancel(true);
                    System.out.println("Task timed out and was cancelled");
                }
            }, 3, TimeUnit.SECONDS);
        }

        executor.shutdown();
    }
}
  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
回答:Java线程池Java中的一个重点知识,并且在Java的工作中经常会遇到,因此在面试中也是必问的面试题目。以下是一些常见的Java线程池面试题: 1. 谈谈什么是线程池? 2. 为什么要使用线程池? 3. 你们哪些地方会使用到线程池? 4. 线程池有哪些作用? 5. 线程池的创建方式有哪些? 6. 线程池底层是如何实现复用的? 7. ThreadPoolExecutor核心参数有哪些? 8. 线程池创建的线程会一直在运行状态吗? 9. 为什么阿里巴巴不建议使用Executors? 10. 线程池的底层实现原理是什么? 11. 线程池队列满了,任务会丢失吗? 12. 线程池的拒绝策略类型有哪些? 13. 线程池如何合理配置参数? 这些问题涵盖了线程池的基本概念、使用场景、实现原理以及相关的配置和策略等方面的知识。了解这些问题能够帮助面试者更好地理解和应用Java线程池。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [java线程池面试题有哪些?java线程池常见面试题](https://blog.csdn.net/muli525/article/details/123553744)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [(一)【Java精选面试题线程池底层实现原理(含答案)](https://blog.csdn.net/qq_30999361/article/details/124924343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

至真源

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值