java并发编程汇总10:线程池框架

主要参考以前写的博客:

1:深入理解线程池、及线程池的使用

2:java.util.concurrent.Executor、ExecutorService、ThreadPoolExecutor、Executors,ThreadFactory

框架性总结:

1、按顺序:java.util.concurrent.Executor、ExecutorService、ThreadPoolExecutor、Executors,ThreadFactory

按以上总结:

  1. Executor:执行器接口,也是最顶层的抽象核心接口, 分离了任务和任务的执行。
  2. ExecutorService:在Executor的基础上提供了执行器生命周期管理,任务异步执行等功能。
  3. ScheduledExecutorService:在ExecutorService基础上提供了任务的延迟执行/周期执行的功能。
  4. Executors:生产具体的执行器的静态工厂
  5. ThreadFactory:线程工厂,用于创建单个线程,减少手工创建线程的繁琐工作,同时能够复用工厂的特性。
  6. AbstractExecutorService:ExecutorService的抽象实现,为各类执行器类的实现提供基础。
  7. ThreadPoolExecutor:线程池Executor,也是最常用的Executor,可以以线程池的方式管理线程。
  8. ScheduledThreadPoolExecutor:在ThreadPoolExecutor基础上,增加了对周期任务调度的支持。
  9. 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(takeLockputLock)来控制线程安全的,因此,也由这两个lock生成了两个对应的condition(notEmptynotFull)来实现可阻塞的插入和删除数据。

两者比较:

相同点:ArrayBlockingQueue和LinkedBlockingQueue都是通过condition通知机制来实现可阻塞式插入和删除元素,并满足线程安全的特性;

不同点

  1.  ArrayBlockingQueue底层是采用的数组进行实现,而LinkedBlockingQueue则是采用链表数据结构;
  2. ArrayBlockingQueue插入和删除数据,只采用了一个lock,而LinkedBlockingQueue则是在插入和删除分别采用了putLocktakeLock,这样可以降低线程由于线程无法获取到lock而进入WAITING状态的可能性,从而提高了线程并发执行的效率。

猜你喜欢

转载自blog.csdn.net/ScorpC/article/details/113914729
今日推荐