Hello,今天给各位童鞋们分享Java线程池,赶紧拿出小本子记下来吧!
1.为什么使用线程池
1.频繁创建和销毁单个线程,浪费资源,并且还会出现频繁GC
2.缺乏统一管理,各线程相互竞争
2.ThreadPoolExecutor
ThreadPoolExecutor有四个重载的构造方法,我们这里来说说参数最多的那一个重载的构造方法,这样大家就知道其他方法参数的含义了,如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
-
各参数详细说明:
这里是7个参数(我们在开发中用的更多的是5个参数的构造方法),OK,那我们来看看这里七个参数的含义:
corePoolSize 线程池中核心线程的数量
maximumPoolSize 线程池中最大线程数量
keepAliveTime 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长
unit 第三个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等
workQueue 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
threadFactory 为线程池提供创建新线程的功能,这个我们一般使用默认即可
handler 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
-
workQueue介绍
1.ArrayBlockingQueue:这个表示一个规定了大小的BlockingQueue,ArrayBlockingQueue的构造函数接受一个int类型的数据,该数据表示BlockingQueue的大小,存储在ArrayBlockingQueue中的元素按照FIFO(先进先出)的方式来进行存取。
2.LinkedBlockingQueue:这个表示一个大小不确定的BlockingQueue,在LinkedBlockingQueue的构造方法中可以传一个int类型的数据,这样创建出来的LinkedBlockingQueue是有大小的,也可以不传,不传的话,LinkedBlockingQueue的大小就为Integer.MAX_VALUE,源码如下:
3.PriorityBlockingQueue:这个队列和LinkedBlockingQueue类似,不同的是PriorityBlockingQueue中的元素不是按照FIFO来排序的,而是按照元素的Comparator来决定存取顺序的(这个功能也反映了存入PriorityBlockingQueue中的数据必须实现了Comparator接口)。
4.SynchronousQueue:这个是同步Queue,属于线程安全的BlockingQueue的一种,在SynchronousQueue中,生产者线程的插入操作必须要等待消费者线程的移除操作,Synchronous内部没有数据缓存空间,因此我们无法对SynchronousQueue进行读取或者遍历其中的数据,元素只有在你试图取走的时候才有可能存在。我们可以理解为生产者和消费者互相等待,等到对方之后然后再一起离开。
-
拒绝策略
AbortPolicy:直接拒绝,并抛出异常,这也是默认的策略。
CallerRunsPolicy:直接让调用execute方法的线程去执行此任务。
DiscardOldestPolicy:丢弃最老的未处理的任务,然后重新尝试执行当前的新任务。
DiscardPolicy:直接丢弃当前任务,但是不抛异常
3.执行过程
当线程数量未达到corePoolSize的时候,就会创建新的线程来执行任务。
当核心线程数已满,就会把任务放到阻塞队列。
当队列已满,并且未达到最大线程数,就会新建非核心线程来执行任务(重要)。
当队列已满,并且达到了最大线程数,则选择一种拒绝策略来执行。
4.其他线程池
1.FixedThreadPool
固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。
该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。
同时使用*的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue迅速增大,存在着耗尽系统资源的问题。
而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown
2.SingleThreadExecutor
可以看到阻塞队例 使用的是LinkedBolckingQueue,且默认大小为Integer.MAX_VALUE,这样的话,如果有大量请求到来,会放入到这个任务队列里,可能会导致OOM;
3.Executors.newCachedThreadPool()
可缓存线程池,先查看线程池中有没有以前建立的线程,如果有就直接使用,如果没有新建一个线程加入线程池中,可缓存线程池
通常用于执行一些生存期很短的异步型任务;线程池为无限大,当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程
缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。
是一个直接提交的阻塞队列,他总会迫使线程池增加新的线程去执行新的任务。
在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,
如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。
如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。
4.ScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行
定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。
scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。
schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。
5.为什么阿里推荐自定义线程池
通过上述源码分析,我们发现newFixedThreadPool和newSingleThreadExecutor方法他们都使用了LinkedBlockingQueue的任务队列,LinkedBlockingQueue的默认大小为Integer.MAX_VALUE。而newCachedThreadPool中定义的线程池大小为Integer.MAX_VALUE。
所以阿里禁止使用Executors创建线程池的原因就是FixedThreadPool和SingleThreadPool的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
CachedThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
6.其他
1.shutDown() 关闭线程池,不影响已经提交的任务
2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程
3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收
4.单例模式创建线程池
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.*;
/**
* 异步任务处理器
*/
public class AsyncTaskExecutor {
/** 线程池保持ALIVE状态线程数 */
public static final int CORE_POOL_SIZE = 10;
/** 线程池最大线程数 */
public static final int MAX_POOL_SIZE = 40;
/** 空闲线程回收时间 */
public static final int KEEP_ALIVE_TIME = 1000;
/** 线程池等待队列 */
public static final int BLOCKING_QUEUE_SIZE = 1000;
/** 业务请求异步处理线程池 */
private static final ThreadPoolExecutor processExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.MICROSECONDS,
new LinkedBlockingQueue<Runnable>(BLOCKING_QUEUE_SIZE),
new TreadFactoryBuilder.setNameFormat("boomoom-thread-pool-%d").build(),
new TreadPoolExecutor.DiscardPolicy());
private AsyncTaskExecutor() {};
/**
* 异步任务处理
*
* @param task 任务
*/
public void execute(Runnable task) {
processExecutor.submit(task);
}
}
懒汉式和饥汉式区别
1.饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变。
2.懒汉式如果想要线程安全必须用双重检验锁并且对象还必须是volatile,防止对象指令重排
好啦,今天的文章就到这里,希望能帮助到屏幕前迷茫的你们