谈谈你对线程、线程池以及使用多线程的经验?
我们知道线程是CPU调度和分配的最小单位,线程的创建的方式有继承Thread类、实现Runnable接口或者实现Callable接口。他们之前的区别是 Java是单继承多实现的,一旦我们创建的线程类集成了Thread类之后就不能继承其他父类了,所以不推荐使用继承的方式,Runnable类是所有多线程类的父接口,通过实现该接口的run()方法;第三种是实现Callable接口,通过实现该接口的call()方法,调用call方法或配合FutureTask调用FutureTask的run方法或者配合Future 通过调用get方法创建线程并可以获取call方法的返回值。Runnable接口和Callable接口的区别是Runnable的run方法是无返回值的,而Callable的call方法是有返回值的,Callable通过配合ExecuterService 、Future使用可以获取call方法的返回值。
至于线程池,线程池是把创建好的线程放入池中,等到需要的时候从池中拿出来使用,不会频繁的去创建和销毁线程,因为创建和销毁线程的花销是比较大的。这样做能提高效率以及方便对线程进行管理,常见的线程池有newFixedThreadPool()、newWorkStealingPool()、newSingleThreadExecutor()、newCacheThreadPool()、newScheduledThreadPool()。
newSingleThreadExecutor()创建的是一个单线程的线程池,适用于需要保证顺序执行的各个任务,并且在任意时间点都不会有多个线程活动的场景。
newFixedThreadPool()创建的是一个固定大小的线程池,采用的是无界阻塞队列,所以线程数量是不会变化的,适用于可以预测线程数量的业务中,
newCacheThreadPool()创建的是一个可以无限扩大的线程池,适用服务器负载比较轻但短期异步任务比较多的场景,默认空闲线程回收时间是60s。
newScheduledThreadPool()创建的是一个定时的的可周期执行的线程池,适用于需要多个后台任务定时执行的场景。
newWorkStealingPool()创建的是一个当前CPU可用数量的线程的线程池,能够合理利用CPU对任务进行并行操作,适用于很耗时的任务。
但是阿里开发手册给出最好不要使用Executors中的创建线程池的方法创建线程,而是采用ThreadPoolExecutor类来创建线程,ThreadPoolExecutor类的构造方法中的参数包含corePoolSize 核心线程数,maximumPoolSize 最大线程数;keepAliveTime 非核心线程存活时间;unit keepAliveTime的时间单位;workQueue 用来存储等待执行任务的阻塞队列;threadFactory 创建线程的工厂类 handler 拒绝策略;
关于核心线程数,最大线程数和空闲线程存活时间之间的关系:
1、当有任务进来时,如果当前线程数小于核心线程数,不管是否有空闲线程,都会创建线程去处理任务
2、当线程数目大于核心线程数目时,有任务进来,则把任务存放到阻塞队列中,等待核心线程空闲时去处理
3、当阻塞队列满了之后,如果当前线程数小于最大线程数,则创建新的线程去处理任务
4、当线程数等于最大线程数并且阻塞队列已经满了,这时会采用拒绝策略。
5、当线程数大于核心线程数,并且线程空闲时间大于keepAliveTime时,会对空闲线程进行回收。
6、如果线程池设置了allowCoreThreadTimeOut(true),核心线程达到空闲时间阈值之后也会被回收。
关于阻塞队列,常见的阻塞队列有:LinkedBlockingQueue、ArrayBlockingQueue、priorityBlockingQueue、DelayQueue、SychonousQueue、LinkedTransfQueue、LinkedBlockingDeque
简单介绍下就是:
LinkedBlockingQueue 是一个链表结构组成的可选边界的阻塞队列
ArrayBlockingQueue 是一个数组结构组成的有界阻塞队列
PriorityBlockingQueue 是一个支持优先级的无界阻塞队列
DelayQueue 是一个使用优先级队列实现的无界阻塞队列
SynchronousQueue 是一个没有缓冲或者说不存储元素的阻塞队列
LinkedTransfQueue 是一个链表结构的无界阻塞队列
LinkedBlockingDeque 是一个双向链表的可选边界的阻塞队列
至于线程工厂,我们可以实现ThreadFactory线程工厂接口的newThread方法,自定义自己的创建线程的方法或使用默认线程工厂类DefaultThreadFactory。
当线程数量等于最大线程数并且阻塞队列已经满了时,线程池会采用拒绝策略阻止任务入队,常见的拒绝策略有:
ThreadPoolExecutor.AbortPolicy 拒绝并抛弃,但是会抛出RejectedExecutionException
ThreadPoolExecutor.DiscardPolicy 拒绝并抛弃任务,不会抛出异常
ThreadPoolExecutor.DiscardOldestPolicy 当新任务到来时,会把阻塞队列头部的任务抛弃,然后执行改任务
ThreadPoolExecutor.CallerRunsPolicy 当任务到来时,会让当前的Executor来执行该任务,但是可能会阻塞当前Executor线程,导致线程池无法进行任务调度。