自定义线程池

线程池简介

  • Java的线程与系统的线程一一对应,创建线程的系统资源开销比较大。

  • 过多的线程会占用更多的系统资源,线程的上下文切换也会带来系统资源开销。

  • 线程池可以对线程进行统一管理,平衡线程与系统资源直接的关系,提高系统资源利用率与程序稳定性。

  • 创建一个自定义线程池代码如下:

    // 使用指定参数创建一个线程池
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    1, // 最大核心线程数量
                    2, // 最大线程数量 = 核心线程数量 + 临时线程数量
                    1000, // 临时线程的最大空闲时间,超过这个时间将会被销毁
                    TimeUnit.MILLISECONDS, // 时间单位
                    new ArrayBlockingQueue<Runnable>(1), // 任务队列的容器
                    new MyThreadFactory(), // 线程创建工厂类
                    new MyRejectedExecutionHandler() // 任务拒绝处理策略
            );
    
    // 向线程池提交一个任务
    executor.submit(new MyRunner("hello"));
    

线程池相关参数

  • corePoolSize:最大核心线程数量,常驻线程。

  • maximumPoolSize:最大线程数量,临时线程。

  • keepAliveTime:临时线程的最大空闲时间,超过这个时间的临时线程将会被销毁。

  • unit:空闲时:间的单位。

  • workQueue:存放任务的阻塞队列。JDK提供的有:

    • LinkedBlockingQueue:链表队列,最大长度为int类型最大值。
    • SynchronousQueue:转发队列,长度为0,只对任务转发,不存储任务。
    • DelayedWorkQueue:延迟队列,数据结构为堆,按照任务延迟时间排序。
  • threadFactory:线程创建工厂类,可以通过实现java.util.concurrent.ThreadFactory接口创建自己的工厂类。

  • handler:任务拒绝策略,线程池无法处理任务时执行的策略,可以通过实现java.util.concurrent.RejectedExecutionHandler接口创建自己的策略。

    JDK提供的策略如下:

    • AbortPolicy:拒绝任务时会抛出一个异常。
    • DiscardPolicy:拒绝任务时不会有任何提示,有数据丢失风险。
    • DiscardOldestPolicy:拒绝任务时丢弃存活时间最长的任务。
    • CallerRunsPolicy:拒绝任务时,将任务给提交的线程执行,同时可以减缓任务提交线程的提交速度,减轻线程池压力。

线程池任务执行

  • 首先任务提交到线程池由核心线程执行。
  • 当线程任务执行达到核心线程最大数量时,将会把任务提交给任务队列存储。
  • 当任务队列满时,就会继续创建临时线程执行任务,直到达到最大线程数量。
  • 任务执行达到最大线程数量时,此时线程池无法再接收新的任务,将会进入拒绝策略的执行。
  • 任务执行数降下后将会根据临时线程空闲时间去销毁临时线程,减少资源的消耗。

JDK内置线程池

线程池实现类使用的阻塞任务队列线程池特点使用注意事项
Executors.newFixedThreadPool(5);LinkedBlockingQueue固定线程数量,任务队列容量为int最大值任务队列可能导致OOM
Executors.newCachedThreadPool();SynchronousQueue核心线程数量为int最大值,任务队列仅作转发线程过多可能导致OOM
Executors.newSingleThreadExecutor();LinkedBlockingQueue核心线程数为一,线程异常将会创建新的线程执行任务,可以保证任务执行的顺序任务队列可能导致OOM
Executors.newScheduledThreadPool(5);DelayedWorkQueue指定频率执行任务,多个核心线程执行任务队列可能导致OOM
Executors.newSingleThreadScheduledExecutor();DelayedWorkQueue指定频率执行任务,单个核心线程执行任务队列可能导致OOM

线程数量选择

  • JDK内置线程有各种缺陷,我们要根据具体的情况定制自己的线程池。

  • CPU计算密集型的任务推荐核心线程数量为cpu核心数的2倍,线程过多会导致频繁的线程上下文切换,从而降低了执行效率。

  • IO密集型的任务推荐线程核心数为cpu核心数的数倍,因为IO读取速度相对较慢,更多的线程可以提高cpu利用率。

  • 《Java并发编程实战》的作者 Brain Goetz 推荐的计算方法:

    线程数 = CPU核心数 *( 1 + 平均等待时间 / 平均工作时间 )
    
  • 过多过少的线程数量都会影响任务执行速度,我们可以根绝任务进行分类,按照不同的线程池进行执行。具体还需注意系统资源的协调。

自定义线程池

package cn.devzyh.learning.threadpool;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 自定义线程池参数
 */
public class ThreadPoolParamTest {

    /**
     * 实现自己的线程创建工厂类
     * 也可以通过第三方工具类创建,比如:
     * 通过com.google.common.util.concurrent.ThreadFactory的Builder来实现
     */
    static class MyThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        public MyThreadFactory() {
            this.namePrefix = "TestPool-" +
                    poolNumber.getAndIncrement() +
                    "-TestThread-";
        }

        @Override
        public Thread newThread(Runnable r) {
            // 指定自定义的线程名称
            return new Thread(r, this.namePrefix + threadNumber.getAndIncrement());
        }
    }

    /**
     * 实现自己的任务拒绝处理策略
     * 超过线程池处理能力的任务会执行此策略
     * Java内置四种拒绝策略:
     * AbortPolicy: 抛出一个AbortExecutionException的异常
     * DiscardPolicy: 丢弃新提交的任务,没有任何通知
     * DiscardOldestPolicy: 丢弃任务队列头部的任务,没有任何通知
     * CallRunsPolicy: 将任务丢给提交任务的线程执行,即可以执行任务又可以延缓新任务的提交
     */
    static class MyRejectedExecutionHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("线程池已满,1秒后重试任务");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executor.submit(r); // 重试
            System.out.println("任务以再次提交到线程池");
        }
    }

    /**
     * 线程任务
     */
    static class MyRunner implements Runnable {
        private String taskName;

        public MyRunner(String name) {
            this.taskName = name;
        }

        @Override
        public void run() {

            System.out.println("开始执行任务[" + this.taskName + "],当前线程名称:" +
                    Thread.currentThread().getName());
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务执行完毕[" + this.taskName + "]");
        }

        public String getTaskName() {
            return this.taskName;
        }
    }

    public static void main(String[] args) {
        // 创建自定义参数的线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                1, // 最大核心线程数量
                2, // 最大线程数量 = 核心线程数量 + 临时线程数量
                1000, // 临时线程的最大空闲时间,超过这个时间将会被销毁
                TimeUnit.MILLISECONDS, // 时间单位
                new ArrayBlockingQueue<Runnable>(1), // 任务队列的容器
                new MyThreadFactory(), // 线程创建工厂类
                new MyRejectedExecutionHandler() // 任务拒绝处理策略
        );

        executor.submit(new MyRunner("core")); // 此时会创建一个核心线程执行任务
        executor.submit(new MyRunner("queue")); // 此时核心线程数达到最大,当前任务会被提交到任务队列等待
        executor.submit(new MyRunner("temp")); // 此时任务队列已满,将创建临时线程执行任务
        executor.submit(new MyRunner("reject")); // 此时超出线程执行任务能力,执行任务拒绝策略

        executor.shutdown(); // 关闭线程池
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值