【并发编程 | 第八篇】深入学习线程池(二)

书接上文,【并发编程 | 第七篇】深入学习线程池(一)-优快云博客

没看过的可以先去看看我写的关于线程池的第一篇博客。

线程池的拒绝策略

线程池的拒绝策略有四种:

AbortPolicy:默认的拒绝策略,会抛 RejectedExecutionException 异常。

CallerRunsPolicy:让提交任务的线程自己来执行这个任务,也就是调用execute 方法的线程。

DiscardOldestPolicy:等待队列会丢弃队列中最老的一个任务,也就是队列中等待最久的任务,然后尝试重新提交被拒绝的任务。

DiscardPolicy:丢弃被拒绝的任务,不做任何处理也不抛出异常。

分别对应着你去银⾏办理业务被经理“薄纱”的四个场景:“我们系统瘫痪了”、“谁叫你来办的你找谁去”、“看你比较急,去队⾥加个塞”、“今天没办法,不行你看改⼀天”。

当线程池无法接受新的任务时,也就是线程数达到 maximumPoolSize,任务队列也满了的时候,就会触发拒绝策略。

如果默认策略不能满⾜需求,可以通过实现 RejectedExecutionHandler 接口来定义自己的淘汰策略。例如:记录 被拒绝任务的⽇志。

自定义拒绝策略:

class CustomRejectedHandler {
    public static void main(String[] args) {
        // ⾃定义拒绝策略
        RejectedExecutionHandler rejectedHandler = (r, executor) -> {
            System.out.println("Task " + r.toString() + " rejected. Queue size: " 
                               + executor.getQueue().size());
        };
        // ⾃定义线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,                      // 核⼼线程数
            4,                      // 最⼤线程数
            10,                     // 空闲线程存活时间
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2),  // 阻塞队列容量
            Executors.defaultThreadFactory(),
            rejectedHandler          // ⾃定义拒绝策略
        );
        for (int i = 0; i < 10; i++) {
            final int taskNumber = i;
            executor.execute(() -> {
                System.out.println("Executing task " + taskNumber);
                try {
                    Thread.sleep(1000); // 模拟任务耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executor.shutdown();
    }
 }

线程池有哪几种阻塞队列呢?

 常用的有五种:

有界队列 ArrayBlockingQueue;

无界队列 LinkedBlockingQueue;

优先级队列 PriorityBlockingQueue;

延迟队列 DelayQueue;

同步队列 SynchronousQueue。

 1. ArrayBlockingQueue:一个有界的先进先出的阻塞队列,底层是一个数组,适合固定大小的线程池。

ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10, true);

2. LinkedBlockingQueue:底层是链表,如果不指定大小,默认大小是 Integer.MAX_VALUE,几乎相当于⼀个无界队列。

3.PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。任务按照其自然顺序或Comparator来排序。

适用于需要按照给定优先级处理任务的场景,比如优先处理紧急任务。

4.DelayQueue:类似于 PriorityBlockingQueue,由⼆叉堆实现的无界优先级阻塞队列。

5.SynchronousQueue::每个插⼊操作必须等待另⼀个线程的移除操作,同样,任何⼀个移除操作都必须等待另一个线程的插⼊操作。

线程池提交的execute和submit有什么区别

execute方法没有返回值,适用于不关心结果和异常的简单任务。

threadsPool.execute(new Runnable() {
 @Override public void run() {
 System.out.println("execute() ⽅法提交的任务");
  }
 })

submit 有返回值,适用于需要获取结果或处理异常的场景。

Future<Object> future = executor.submit(harReturnValuetask);
 try { Object s = future.get(); } 
catch (InterruptedException e | ExecutionException e) {
 // 处理⽆法执⾏任务异常
} finally {
 // 关闭线程池 executor.shutdown();
 }

线程池如何关闭? 

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

shutdown不会立即停止线程池,而是等待所有任务执⾏完毕后再关闭线程池。

ExecutorService executor = Executors.newFixedThreadPool(3);
 executor.execute(() -> System.out.println("Task 1"));
 executor.execute(() -> System.out.println("Task 2"));
 executor.shutdown(); // 不会⽴刻关闭,⽽是等待所有任务执⾏完毕

shutdownNow 会尝试通过⼀系列动作来停止线程池,包括停⽌接收外部提交的任务、忽略队列⾥等待的任务、尝试将正在跑的任务 interrupt 中断。 

ExecutorService executor = Executors.newFixedThreadPool(3);
 executor.execute(() -> {
 try {
 Thread.sleep(5000); // 模拟⻓时间运⾏任务
System.out.println("Task executed");
  } 
catch (InterruptedException e) {
 System.out.println("任务被中断");
  }
 });
 List<Runnable> unexecutedTasks = executor.shutdownNow(); // ⽴即关闭线程池
System.out.println("未执⾏的任务数: " + unexecutedTasks.size());

需要注意的是,shutdownNow 不会真正终止正在运⾏的任务,只是给任务线程发送 interrupt 信号,任务是否能 真正终止取决于线程是否响应 InterruptedException。 

线程池的线程数如何配置?

首先,分析任务是CPU密集型还是IO密集型。

1.如果是CPU密集型,我们应该尽量减少上下文切换,来优化CPU效率。一般来讲,核心线程数设置为处理器的核心数或核心数+1是较为理想的选择。

+1是为了以备不时之需,如果某线程因等待系统资源而阻塞时,可以有多余的线程顶上去,不至于影响整体性能。

2.对于IO密集型任务,由于线程经常处于等待状态,等待IO操作完成,所以我们可以设置更多的线程提高高并发,比如CPU核心数的两倍。

最后,我会根据业务需求和系统资源来调整线程池的其他参数,⽐如最⼤线程数、任务队列容量、非核⼼线程的空 闲存活时间等。

ThreadPoolExecutor executor = new ThreadPoolExecutor(
 cores, // 核⼼线程数设置为CPU核⼼数
cores * 2, // 最⼤线程数为核⼼数的两倍
60L, TimeUnit.SECONDS, // ⾮核⼼线程的空闲存活时间
new LinkedBlockingQueue<>(100) // 任务队列容量
)

有哪几种常见的线程池?

主要有四种:

固定大小的线程池 Executors.newFixedThreadPool(int nThreads); ,适合用于任务数量确定,且对线程数有 明确要求的场景。例如,IO 密集型任务、数据库连接池等。

缓存线程池 Executors.newCachedThreadPool(); ,适用于短时间内任务量波动较大的场景。例如,短时间内有大量的文件处理任务或网络请求。

定时任务线程池 Executors.newScheduledThreadPool(int corePoolSize); ,适用于需要定时执⾏任务的场景。例如,定时发送邮件、定时备份数据等。

单线程线程池 Executors.newSingleThreadExecutor(); ,适用于需要按顺序执⾏任务的场景。例如,⽇志记录、文件处理等

不管是 FixedThreadPool、CachedThreadPool,还是 SingleThreadExecutor 和 ScheduledThreadPoolExecutor,它们本质上都是 ThreadPoolExecutor 的不同配置。

线程池异常怎么处理?

常⻅的处理方式有,使用try-catch 捕获、使用Future 获取异常、自定义ThreadPoolExecutor 重写 afterExecute方法、使用 UncaughtExceptionHandler 捕获异常。

1.try-catch方法:

executor.execute(() -> {
 try {
 System.out.println("任务开始");
 int result = 1 / 0; // 除零异常
  } 
catch (Exception e) {
 System.err.println("捕获异常:" + e.getMessage());
  }
 });

2.使用Future获得异常:

Future<Object> future = executor.submit(() -> {
 System.out.println("任务开始");
 int result = 1 / 0; // 除零异常
return result;
 });
 try {
 future.get();
 } catch (InterruptedException | ExecutionException e) {
 System.err.println("捕获异常:" + e.getMessage());
 }

 3.自定义 ThreadPoolExecutor 重写 afterExecute方法。

ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
 new LinkedBlockingQueue<Runnable>()) {
 @Override
 protected void afterExecute(Runnable r, Throwable t) {
 super.afterExecute(r, t);
 if (t != null) {
 System.err.println("捕获异常:" + t.getMessage());
  }
  }
 };
 executor.execute(() -> {
 System.out.println("任务开始");
 int result = 1 / 0; // 除零异常
});

 4.、使用UncaughtExceptionHandler 捕获异常。

ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
 new LinkedBlockingQueue<Runnable>());
 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
 executor.setThreadFactory(new ThreadFactory() {
 @Override
 public Thread newThread(Runnable r) {
 Thread thread = new Thread(r);
 thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
 @Override
 public void uncaughtException(Thread t, Throwable e) {
 System.err.println("捕获异常:" + e.getMessage());
  }
  });
 return thread;
  }
 });
 executor.execute(() -> {
 System.out.println("任务开始");
 int result = 1 / 0; // 除零异常
})

如果项⽬使用execute() ,不关⼼任务返回值,建议使用UncaughtExceptionHandler:

thread.setUncaughtExceptionHandler((t, e) -> 
System.err.println("线程 " + t.getName() + " 捕获到异常:" + e.getMessage()))

如果项⽬使用submit() ,关⼼任务返回值,建议使用Future:

Future<?> future = executor.submit(task);
 try {
 future.get();
 } catch (ExecutionException e) {
 System.err.println("捕获异常:" + e.getCause());
 }

如果想要全局捕获所有任务异常,建议重写 afterExecute ⽅法:

class MyThreadPoolExecutor extends ThreadPoolExecutor {
 @Override
 protected void afterExecute(Runnable r, Throwable t) {
 if (t == null && r instanceof Future<?>) {
 try { ((Future<?>) r).get(); } catch (Exception e) { System.err.println("任务
异常:" + e.getCause()); }
  }
  }
 }

 如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值