java 线程池ThreadPoolExecutor

线程池的基本使用,以及常见问题


想线程池提交任务,执行流程
线
提交任务
执行任务←否核心线程池是否已满
↓是
将任务放入缓存队列←否等待队列是否已满
↓是
创建线程执行任务←否线程池是否达到最大线程
↓是
执行拒绝策略处理无法处理的任务

使用线程池三种方式

ExecutorService threadPool = new Executors.newFixedThreadPool(5)
//一池固定数量线程
ExecutorService threadPool = new Executors.newSingleThreadPool();
//一池一线线程
ExecutorService threadPool = new Executors.newCachedThreadPool();
//一池n线程
 threadPool.execute(()->{
                   
                    System.out.println(Thread.currentThread().getName()+"处理任务");
                });

七大参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          timeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  1. corePoolSize
    线程池中的窗柱核心线程数
  2. maximumPoolSize
    线程池能够容纳同时执行的最大线程数,此值必须大于等于一;
  3. keepAliveTime
    多余的空闲线程的存活时间。(当线程池线程数量超过corePoolSize时,空闲时间达到keepAliveTime时会被销毁直到剩下corePoolSize个线程为止)
  4. unit
    keepAliveTime的单位
  5. workQueue
    任务队列,被提交但尚未执行的任务
  6. threadFactory
    表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可
  7. handler
    拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)

拒绝策略

  1. AbortPolicy(默认)
    直接抛出RejectedExecutionException异常,阻止系统正常运行

  2. CallerRunsPolicy
    “调用者运行” 一种调节机制,不抛异常也不抛弃任务,而是将某任务回退给调用者。

  3. DiscardOldestPolicy
    抛弃队列中等待最久的任务,将新任务加入队列,尝试再次提交

  4. DiscardPolicy
    直接丢弃任务,不予任何处理(如果允许丢弃,此方案最佳)

如果这几种拒绝策略不能满足需求,我们还可以通过重写RejectedExecutionHandler的rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor)方法

package java.util.concurrent;

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
}

面试填坑

前面不是说有三种使用线程池的方式吗。那你平时用哪个比较多?
回答:三选一 ×
回答:哪个都不用,我一般都会使用
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
timeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

自定义线程池的一些参数。
因为上述三种方法,默认阻塞队列虽说是有界的但是,最大值是Integer.MAX不太现实。
所以我们需要手动传入一些参数,来保证业务的正常运行。

在ThreadPoolExecutor中还有三个钩子方法(默认是空方法),我们可以重写该方法来执行一些操作

protected void beforeExecute(Thread var1, Runnable var2) {
    }

    protected void afterExecute(Runnable var1, Throwable var2) {
    }

    protected void terminated() {
    }

在ThreadPoolExecutor runWorker方法里调用

final void runWorker(ThreadPoolExecutor.Worker var1) {
        Thread var2 = Thread.currentThread();
        Runnable var3 = var1.firstTask;
        var1.firstTask = null;
        var1.unlock();
        boolean var4 = true;

        try {
            while(var3 != null || (var3 = this.getTask()) != null) {
                var1.lock();
                if ((runStateAtLeast(this.ctl.get(), 536870912) || Thread.interrupted() && runStateAtLeast(this.ctl.get(), 536870912)) && !var2.isInterrupted()) {
                    var2.interrupt();
                }

                try {
                    this.beforeExecute(var2, var3);
                    Object var5 = null;

                    try {
                        var3.run();
                    } catch (RuntimeException var28) {
                        var5 = var28;
                        throw var28;
                    } catch (Error var29) {
                        var5 = var29;
                        throw var29;
                    } catch (Throwable var30) {
                        var5 = var30;
                        throw new Error(var30);
                    } finally {
                        this.afterExecute(var3, (Throwable)var5);
                    }
                } finally {
                    var3 = null;
                    ++var1.completedTasks;
                    var1.unlock();
                }
            }

            var4 = false;
        } finally {
            this.processWorkerExit(var1, var4);
        }

    }
合理使用线程池,能够提高性能,方便线程资源的监控与管理。因为频繁创建销毁线程是比较消耗性能的。使用线程池当任务进来的时候不需要等待线程创建就可以直接运行可以提高效率。

但如果线程池使用的不合理,往往会出生反面效果。
这里举一个比较极端的例子来理解。


比如说设置 CoreThread 为 1 阻塞队列的容量为50 MaxThread数 为 10 有5个任务需要线程池执行,但第一个任务里写一个死循环。虽然只有5个任务,但由于CoreThread线程为1 且第一个任务有时死循环。导致其他四个任务放在队列中,等待第一个任务执行完毕,这样无限等待其他4个人任务也不会创建新线程去执行。
自定义创建线程池加优雅的关闭线程池
public class cloud {
    private static final int CORE_NUMBER = Runtime.getRuntime().availableProcessors();
    public static void main(String[] args) {
        //io密集型
        System.out.println("cpu core number :"+CORE_NUMBER);
        ThreadPoolExecutor instance = MyThreadPool.getInstance();
        for (int i =0; i < 6;i++) {
            Runnable runnable = new Runnable(){
                @Override
                public void run() {
                    try {
                        System.out.println("run thread id "+Thread.currentThread().getId());
                        Thread.sleep(1000 * 5);
                        System.out.println("stop thread id "+Thread.currentThread().getId());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
           System.out.println("thread pool add task "+ (i+1));
           instance.execute(runnable);
        }

			  //jvm的一个钩子函数,在jvm关闭的时候调用
        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            System.out.println("jvm closing");
            //关闭线程池,shutdown() 不接受新任务,正在执行中的任务继续执行。shutdownNow() 会调用interrupt() 终端线程执行,将未执行任务返回。
            instance.shutdown();
            while(true) {
                try {
                    if (instance.awaitTermination(500, TimeUnit.MILLISECONDS)) {
                        System.out.println("thread pool is closed");
                        break;
                    }else {
                        System.out.println("wait pool closing");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }));

		//为了查看结果,将线程阻塞在这里。
        try {
            TimeUnit.SECONDS.sleep(1000*300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


  static class MyThreadPool {
        private static final  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_NUMBER,
                CORE_NUMBER,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory());
        private MyThreadPool() {
        }
        public static ThreadPoolExecutor getInstance() {
            return threadPoolExecutor;
        }
    }
}

线程池使用场景分类
(1) cpu 密集型
执行任务时,cpu的使用较高,任务执行较快。
一般为了避免cpu上下文来回切换,浪费时间,CoreThreadNumber设置为cpu核心数。
(2) io 密集型
执行任务时,cpu空闲时间比较多,大多时间在等待io操作,或者网络操作响应。
(3)混合型
这种情况属于介于1和2之间的一种类型。例如web服务器一次http请求响应就是比较典型的,既有一些逻辑计算处理又有网络请求,请求数据库。
混合型有一个公式可以计算最佳的核心线程数量
最佳核心线程数量=((线程等待时间/cpu执行时间)+1) * cpu 核心数

线程池也和线程一样具有几种状态
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值