主要参考以前写的博客:
2:java.util.concurrent.Executor、ExecutorService、ThreadPoolExecutor、Executors,ThreadFactory
框架性总结:
1、按顺序:java.util.concurrent.Executor、ExecutorService、ThreadPoolExecutor、Executors,ThreadFactory
按以上总结:
- Executor:执行器接口,也是最顶层的抽象核心接口, 分离了任务和任务的执行。
- ExecutorService:在Executor的基础上提供了执行器生命周期管理,任务异步执行等功能。
- ScheduledExecutorService:在ExecutorService基础上提供了任务的延迟执行/周期执行的功能。
- Executors:生产具体的执行器的静态工厂。
- ThreadFactory:线程工厂,用于创建单个线程,减少手工创建线程的繁琐工作,同时能够复用工厂的特性。
- AbstractExecutorService:ExecutorService的抽象实现,为各类执行器类的实现提供基础。
- ThreadPoolExecutor:线程池Executor,也是最常用的Executor,可以以线程池的方式管理线程。
- ScheduledThreadPoolExecutor:在ThreadPoolExecutor基础上,增加了对周期任务调度的支持。
- ForkJoinPool:Fork/Join线程池,在JDK1.7时引入,是实现Fork/Join框架的核心类。
2、ThreadPoolExecutor
ExecutorService中的submit(),invokeAll(),invokeAny()都是调用的execute方法,所以execute是核心中的核心;
它的重要参数:corePoolSize、maximumPoolSize,拒绝策略,阻塞队列;
- newFixedThreadPool:该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值;
- newSingleThreadExecutor:只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。
- newCachedThreadPool:缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。
- newScheduledThreadPool:定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。
关于阻塞队列:
- 选择合适的阻塞队列。newFixedThreadPool和newSingleThreadExecutor都使用了无界的阻塞队列,无界阻塞队列会有消耗很大的内存,如果使用了有界阻塞队列,它会规避内存占用过大的问题,但是当任务填满有界阻塞队列,新的任务该怎么办?在使用有界队列是,需要选择合适的拒绝策略,队列的大小和线程池的大小必须一起调节。对于非常大的或者无界的线程池,可以使用SynchronousQueue来避免任务排队,以直接将任务从生产者提交到工作者线程。
- 由于,核心线程池是很容易满的,所以当使用SynchronousQueue时,一般需要将maximumPoolSizes 设置得比较大,否则入队很容易失败,最终导致执行拒绝策略,这也是为什么Executors工作默认提供的缓存线程池使用SynchronousQueue作为任务队列的原因。
- SynchronousQueue是没有容量的,而且采用了无锁算法,所以性能较好,但是每个入队操作都要等待一个出队操作,反之亦然。
拒绝策略:
- AbortPolicy:丢弃任务并抛出RejectedExecutionException
- CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
- DiscardOldestPolicy:丢弃队列中最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
- DiscardPolicy:丢弃任务,不做任何处理。
3、ExecutorService:
ExecutorService的核心方法是submit方法——用于提交一个待执行的任务,如果读者阅读ThreadPoolExecutor的源码,会发现它并没有覆写submit方法,而是沿用了父类AbstractExecutorService的模板,然后自己实现了execute方法。
4、ArrayBlockingQueue和LinkedBlockingQueue实现原理详解、比较:
ArrayBlockingQueue内部是采用数组进行数据存储的(属性items
),为了保证线程安全,采用的是ReentrantLock lock
,为了保证可阻塞式的插入删除数据利用的是Condition,当获取数据的消费者线程被阻塞时会将该线程放置到notEmpty等待队列中,当插入数据的生产者线程被阻塞时,会将该线程放置到notFull等待队列中。
LinkedBlockingQueue是用链表实现的有界阻塞队列,当构造对象时为指定队列大小时,队列默认大小为Integer.MAX_VALUE
。
可以看出与ArrayBlockingQueue主要的区别是:LinkedBlockingQueue在插入数据和删除数据时分别是由两个不同的lock(takeLock
和putLock
)来控制线程安全的,因此,也由这两个lock生成了两个对应的condition(notEmpty
和notFull
)来实现可阻塞的插入和删除数据。
两者比较:
相同点:ArrayBlockingQueue和LinkedBlockingQueue都是通过condition通知机制来实现可阻塞式插入和删除元素,并满足线程安全的特性;
不同点:
- ArrayBlockingQueue底层是采用的数组进行实现,而LinkedBlockingQueue则是采用链表数据结构;
- ArrayBlockingQueue插入和删除数据,只采用了一个lock,而LinkedBlockingQueue则是在插入和删除分别采用了
putLock
和takeLock
,这样可以降低线程由于线程无法获取到lock而进入WAITING状态的可能性,从而提高了线程并发执行的效率。