java多线程 — 线程池

  • Post author:
  • Post category:java




一 什么是线程池

简单理解就是装有线程的池子,和数据库连接池的概念是一样的.事先准备好一资源,当有人需要用的时候,直接过来资源,用完之后将资源归还,通过线程池可以达到多个线程的复用

程序的执行肯定会消耗占用系统的资源,我们可以通过池化技术(线程池)来优化资源的使用,使线程复用,这样可以大大降低资源的消耗,提高系统响应速度,更方便我们管理线程,



二 线程池的使用

我们可以通过ThreadPoolExecutor来创建一个线程池:

我们来分析一下源码

public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
                           int maximumPoolSize, //最大核心线程池大小
                           long keepAliveTime, //空闲时间数量
                           TimeUnit unit, //超时单位
                           BlockingQueue<Runnable> workQueue, //阻塞队列
                           ThreadFactory threadFactory, //线程工厂,创建线程,一般不用动
                           RejectedExecutionHandler handler) { //拒绝策略
     if (corePoolSize < 0 ||
         maximumPoolSize <= 0 ||
         maximumPoolSize < corePoolSize ||
         keepAliveTime < 0)
         throw new IllegalArgumentException();
     if (workQueue == null || threadFactory == null || handler == null)
         throw new NullPointerException();
     this.acc = System.getSecurityManager() == null ?
             null :
             AccessController.getContext();
     this.corePoolSize = corePoolSize;
     this.maximumPoolSize = maximumPoolSize;
     this.workQueue = workQueue;
     this.keepAliveTime = unit.toNanos(keepAliveTime);
     this.threadFactory = threadFactory;
     this.handler = handler;
 }
  • corePoolSize 线程池的核心线程大小, 相当于公司的正式员工
  • maximumPoolSize 最大的核心线程数,
  • keepAliveTime 线程池的工作线程空闲后,保持存活的时间, 如果任务很多的,并且每个任务执行的时间比较短情况下, 可以调大时间,提高线程的利用率.
  • TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。
  • BlockingQueue workQueue,
  • threadFactory:创建线程的工厂,一般用默认即可
  • handler:拒绝策略,当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常。


四种拒绝策略

  • 第一种拒绝策略 new ThreadPoolExecutor.AbortPolicy(),也是默认的拒绝策略,如果还有任务需要继续执行,会抛出异常,new ThreadPoolExecutor.AbortPolicy()如果这个类创建了最大线程数,也就是线程池可以承载的是最大的线程数了,会正常执行,并且阻塞队列中的任务已经存储满了,接下来如过在假如一个线程就会

    抛出异常

    ,java.util.concurrent.RejectedExecutionException
  • 第二种拒绝策略 new ThreadPoolExecutor.CallerRunsPolicy() ,这种拒绝策略是哪个线程提交的任务,哪个线程去执行,

    哪里来哪里去

    ,也就是main线程去执行
  • 第三种拒绝策略,new ThreadPoolExecutor.DiscardPolicy(), 如果队列满了

    不会抛出异常

    ,直接

    丢掉任务

    ,
  • 第四种拒绝策略,new ThreadPoolExecutor.DiscardOldestPolicy(),有新的任务需要执行时,会尝试

    和最早的竞争线程资源

    ,也不会抛出异常.


手动创建一个线程池

public class Test {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try {
            for (int i = 0; i < 9; i++) {
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName() + "  OK");
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPoolExecutor.shutdown();
        }
    }
}


线程池的处理流程


在这里插入图片描述

  • 当使用者提交任务之后,
  • 首先会判断核心线程池数量是否已满,核心线程数没满则创建线程执行任务,如果核心线程数已满,会进入第二部阻塞队列进行判断
  • 阻塞队列没有满,则进入阻塞队列等待 如果阻塞队列已满,则进入第三步最大线程数判断
  • 如果线程数量未达到最大线程数,则创建新的线程执行任务,如果达到最大线程数,按照拒绝策略处理无法执行的任务


如何合理的配置线程池


要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。

任务的性质:CPU密集型任务、IO密集型任务

任务的优先级:高、中和低。

任务的依赖性:是否依赖其他系统资源,如数据库连接

  • CPU密集型任务尽可能配置小的线程,几核CPU就是最大线程就是几,可以保持CPU的效率最高 Runtime.getRuntime().availableProcessors() 方法获得当前设备的CPU个数。
  • IO密集型 这类人物特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度),对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度,例如程序有15个大型IO任务, 十分消耗资源, 可以将最大的线程数设置为2倍,
  • 优先级别不同的任务我们可以使用优先级队列来处理,让优先级较高的任务先执行,但需要注意的是,如果有优先级高的任务一直在队列中,那么优先级低的任务永远可能不执行
  • 任务的依赖性,比如依赖数据库连接池的任务,线程提交sql后需要等待数据库返回结果,等待的时间越长,CPU利用率越低,此时和IO密集型相似,将线程数设置的更大,这样才能更好的利用CPU



三 使用Executors工具类创建线程池

  1. 创建无大小限制的线程池:public static ExecutorService newCachedThreadPool()
  2. 创建固定大小的线程池:public static ExecutorService newFixedThreadPool(int

    nThreads)
  3. 单线程池:public static ExecutorService newSingleThreadExecutor()


源码分析

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}


本质都是new ThreadPollExecutor



Executors默认快捷的创建方式一般来说是不建议使用的,本质创建线程池的时候,没有指定创建线程的工厂类以及指定的拒绝策略,如果提交任务的速度比执行任务的速度快,阻塞队列中的任务堆积会越来越多,如果超过java进程的内存,可能会导致OOM,使得整个java进程垮掉



版权声明:本文为supermanman_原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。