Java 线程池

1.前言

本文目标研究的主角是类ThreadPoolExecutor,分为3步走:

  • 搞懂线程池基本概念和一些背景问题;
  • 搞懂线程池底层实现类ThreadPoolExecutor里边各个参数含义,尤其是核心线程池数量和最大线程池数量对整个线程池的影响;
  • 搞懂JDK线程池工厂类Executors对ThreadPoolExecutor的几种封装的使用;

2.背景介绍

2.1.最基本的问题思考;

  • 线程是什么?
    线程是一个容器,它用来承载任务,执行任务;
    它是一个任务执行器,就像一把枪,而任务就像是子弹,体现在java就是Thread.start();

  • 任务是什么?
    任务就像是子弹,它需要一个线程来装载它,体现在java就是Runnable;

  • 线程池是什么?
    线程池就像是多个线程的集合,他通过复用线程Thread来完成工作;
    本文要研究的ThreadPoolExecutor,它其实有2层含义:
    ThreadPool:容器性质Executor:任务执行性质

  • 线程池的优点
    a. 重用存在的线程,减少对象创建、消亡的开销,性能佳;
    b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞;
    c. 提供定时执行、定期执行、单线程、并发数控制等功能;

2.2.线程池出现的原因,解决了什么问题?

  • JDK5开始出现的线程池;
  • 为每个请求创建一个新线程的开销很大,所以需要复用线程;
  • 除了创建和销毁线程的开销之外,活动的线程也消耗系统资源(线程的生命周期!);
  • 在一个JVM里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”;
  • 一些操作系统是有最大线程数量限制的。当运行的线程数量逼近这个值的时候,操作系统会变得不稳定。这也是我们要限制线程数量的原因。
  • 每次new Thread新建对象性能差;
  • 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom;
  • 缺乏更多功能,如定时执行、定期执行、线程中断;

2.3.使用线程池的风险

虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足,并发错误,线程泄漏,请求过载。

2.4.有效使用线程池的准则

只要您遵循几条简单的准则,线程池可以成为构建服务器应用程序的极其有效的方法:

  • (1)不要对那些同步等待其它任务结果的任务排队。这可能会导致上面所描述的那种形式的死锁,在那种死锁中,所有线程都被一些任务所占用,这些任务依次等待排队任务的结果,而这些任务又无法执行,因为所有的线程都很忙。
  • (2)在为时间可能很长的操作使用合用的线程时要小心。如果程序必须等待诸如 I/O 完成这样的某个资源,那么请指定最长的等待时间,以及随后是失效还是将任务重新排队以便稍后执行。这样做保证了:通过将某个线程释放给某个可能成功完成的任务,从而将最终取得 某些 进展。
  • (3)理解任务。要有效地调整线程池大小,您需要理解正在排队的任务以及它们正在做什么。它们是 CPU 限制的(CPU-bound)吗?它们是 I/O 限制的(I/O-bound)吗?您的答案将影响您如何调整应用程序。如果您有不同的任务类,这些类有着截然不同的特征,那么为不同任务类设置多个工作队 列可能会有意义,这样可以相应地调整每个池。

3.ThreadPoolExecutor里边各个参数含义

一共4个构造,我就挑选个最长的构造来看参数,因为这样够全;

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is 
     * blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • int corePoolSize //直译为核心线程数,实际上是线程池维护线程的最少数量
  • int maximumPoolSize //线程池维护线程的最大数量
  • long keepAliveTime //线程池维护线程所允许的空闲时间
  • TimeUnit unit //线程池维护线程所允许的空闲时间的单位
  • BlockingQueue workQueue //线程池所使用的缓冲队列,任务队列,被提交但未被执行的任务存放容器
  • ThreadFactory threadFactory //用于创建一个新线程的线程工厂
  • RejectedExecutionHandler handler //拒绝策略,当任务太多来不及处理时的拒绝策略

3.1.关于corePoolSize(核心线程数)和maximumPoolSize(最大线程数)

  • 如果池中的实际线程数小于corePoolSize,无论是否其中有空闲的线程,都会给新的任务产生新的线程;
  • 如果池中的线程数大于corePoolSize并且小于maximumPoolSize,而又有空闲线程,就给新任务使用空闲线程,如没有空闲线程,则产生新线程;
  • 如果池中的线程数=maximumPoolSize,则有空闲线程使用空闲线程,否则新任务放入workQueue。(线程的空闲只有在workQueue中不再有任务时才成立);

3.2.关于keepAliveTime和TimeUnit unit

keepAliveTime的意思是空闲线程存活时间;
TimeUnit unit是keepAliveTime的时间单位;

3.3.关于BlockingQueue workQueue

线程池所使用的缓冲队列,任务队列,被提交但未被执行的任务存放容器

3.4.关于ThreadFactory threadFactory

用于创建一个新线程的线程工厂

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

是个接口,核心方法newThread;
一般不同的库会有不同的实现,JDK里的就是默认的DefaultThreadFactory;

DefaultThreadFactory#newThread()

    public Thread newThread(@NonNull Runnable runnable) {
        String threadName = namePrefix + threadNumber.getAndIncrement();
        ARouter.logger.info(Consts.TAG, "Thread production, name is [" + threadName + "]");
        Thread thread = new Thread(group, runnable, threadName, 0);
        if (thread.isDaemon()) {   //设为非后台线程
            thread.setDaemon(false);
        }
        if (thread.getPriority() != Thread.NORM_PRIORITY) { //优先级为normal
            thread.setPriority(Thread.NORM_PRIORITY);
        }

        // 捕获多线程处理中的异常
        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread thread, Throwable ex) {
                ARouter.logger.info(Consts.TAG, "Running task appeared exception! Thread [" + thread.getName() + "], because [" + ex.getMessage() + "]");
            }
        });
        return thread;
    }

3.5.关于RejectedExecutionHandler handler

拒绝策略,当任务太多来不及处理时的拒绝策略

4.JDK中的线程池工厂类Executors对ThreadPoolExecutor的几种封装的使用

4.1.类结构图

顶层为接口类Excutor;

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

就一个方法,所以又一个扩展接口ExecutorService:

接着是俩个实现类:
public interface ScheduledExecutorService extends ExecutorService
public abstract class AbstractExecutorService implements ExecutorService

对应俩个子类:
public class ThreadPoolExecutor extends AbstractExecutorService
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService

没发现Executors身影,因为它只是一个调用者,完全独立存在,关系是:Executors has Executor;

4.2.Executors创建不同类型线程池

java线程池概念上有四种,java提供了一个工厂类Executors来帮我们创建不同的线程池,分别是:

  • newFixedThreadPool //定长线程池 —— LinkedBlockingQueue()
  • newCachedThreadPool //无界线程池 —— new SynchronousQueue()
  • newScheduledThreadPool //定长定时线程池 —— ScheduledThreadPoolExecutor(corePoolSize)
  • newSingleThreadExecutor //单线程化的线程池 —— LinkedBlockingQueue()

这四种只是概念,并没有真实的.java文件存在。他们都是ThreadPoolExecutor的不同实现,并且都是通过工厂类Executors构造出来的;

可以看到我后面又跟了个破折号XXXBlockQueue,实际上,Excutors创建的四种线程池的特征,核心实际上是对应的四种不同的容器BlockingQueue的特征

//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService executorService1 = Executors.newFixedThreadPool(5);

//创建一个无界线程池
ExecutorService executorService = Executors.newCachedThreadPool();

//ScheduledExecutorService也是ExecutorService的子接口
//创建一个定长线程池,支持定时及周期性任务执行。
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService executorService2 = Executors.newSingleThreadExecutor();

4.2.1.FixedThreadPool

定长线程池,可控制线程最大并发数,超出的线程会在队列中等待;

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

测试

创建一个定长线程池,我这里的size是5,然后调用submit方法,提交25个任务,从打印结果来看,每次规律性的从池子里取出5个线程;


   ExecutorService executorService = Executors.newFixedThreadPool(5);

        Runnable task = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName());
            }
        };


        for (int i = 0; i < 25; i++) {
            executorService.submit(task);
        }


	线程:pool-1-thread-2
	线程:pool-1-thread-1
	线程:pool-1-thread-3
	线程:pool-1-thread-5
	线程:pool-1-thread-4
	
	线程:pool-1-thread-2
	线程:pool-1-thread-3
	线程:pool-1-thread-1
	线程:pool-1-thread-5
	线程:pool-1-thread-4
	
	线程:pool-1-thread-2
	线程:pool-1-thread-1
	线程:pool-1-thread-5
	线程:pool-1-thread-3
	线程:pool-1-thread-4
	
	线程:pool-1-thread-2
	线程:pool-1-thread-5
	线程:pool-1-thread-3
	线程:pool-1-thread-1
	线程:pool-1-thread-4
	
	线程:pool-1-thread-2
	线程:pool-1-thread-3
	线程:pool-1-thread-5
	线程:pool-1-thread-1
	线程:pool-1-thread-4

4.2.2.CachedThreadPool

无界线程池,看他的构造内size参数,Integer.MAX_VALUE

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

测试
从结果看,无界线程池,当我submiit25个任务线程,它是一波出来的。中间并没有停顿。并且从打印信息来看,也是创建了25个线程。


    ExecutorService executorService = Executors.newCachedThreadPool();

        Runnable task = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName());
            }
        };

        for (int i = 0; i < 25; i++) {
            executorService.submit(task);
        }


	线程:pool-1-thread-3
	线程:pool-1-thread-4
	线程:pool-1-thread-1
	线程:pool-1-thread-5
	线程:pool-1-thread-8
	线程:pool-1-thread-16
	线程:pool-1-thread-11
	线程:pool-1-thread-12
	线程:pool-1-thread-9
	线程:pool-1-thread-10
	线程:pool-1-thread-6
	线程:pool-1-thread-13
	线程:pool-1-thread-21
	线程:pool-1-thread-18
	线程:pool-1-thread-17
	线程:pool-1-thread-25
	线程:pool-1-thread-22
	线程:pool-1-thread-14
	线程:pool-1-thread-24
	线程:pool-1-thread-23
	线程:pool-1-thread-2
	线程:pool-1-thread-19
	线程:pool-1-thread-7
	线程:pool-1-thread-15
	线程:pool-1-thread-20

4.2.3.SingleThreadExecutor

一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行;

   public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

测试

还是submmit 25个任务,但是它一秒执行一个任务(因为我逻辑里睡了一秒),可以从构造来看,它的最大最小值poolsize都是1;

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Runnable task = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName());
            }
        };


        for (int i = 0; i < 25; i++) {
            executorService.submit(task);
        }

    }
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1
	线程:pool-1-thread-1

4.2.4.ScheduledThreadPool

一个定长线程池,支持定时周期性任务执行和延迟执行任务;

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

测试
延迟执行好理解,我这里写的就是延迟3秒后执行,所以我直接注掉了;
周期性执行是下面那个调用scheduleAtFixedRate方法的;

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(25);

        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("线程:" + Thread.currentThread().getName());
            }
        };


//        //延迟
//        for (int i = 0; i < 5; i++) {
//            scheduledExecutorService.schedule(task, 3, TimeUnit.SECONDS);
//        }

        //定期
        for (int i = 0; i < 5; i++) {
            scheduledExecutorService.scheduleAtFixedRate(task, 3, 3, TimeUnit.SECONDS);
        }

5.Demo

本文测试代码:
https://github.com/zj614android/designPattern/tree/master/app/src/main/java/com/zj/多线程/threadpoll

6.Thanks

https://www.cnblogs.com/pairwinter/p/4007864.html //ThreadPoolExecutor参数解析
https://blog.youkuaiyun.com/scherrer/article/details/50708737 //Java ThreadPoolExecutor线程池原理及源码分析
http://www.cnblogs.com/xinxindiandeng/p/6383311.html //为什么要用线程池
http://www.cnblogs.com/WangHaiMing/p/8798709.html //BlockingQueue深入解析-BlockingQueue看这一篇就够了
https://www.cnblogs.com/KingIceMou/p/8075343.html //BlockingQueue
https://www.cnblogs.com/aaron911/p/6213808.html //java常用的几种线程池比较

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值