Java中的线程池原理

来源:互联网 发布:淘宝红包链接转手机端 编辑:程序博客网 时间:2024/06/07 03:09

Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池,在开发过程中,合理地使用线程池能够带来三个好处:

1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

2)能够提高响应速度。当任务到达时,任务可以不需要等到线程创建就能够立即执行。

3)提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优、监控。

一、线程池实现原理

ThreadPoolExecutor机制


Executors是Java线程池的工厂类,通过它可以初始化一个符合业务需求的线程池。

三种不同类型的线程池创建如下:



其本质是通过不同的参数初始化一个ThreadPoolExecutor对象,具体参数描述如下:

corePoolSize

线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

maximumPoolSize

线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;

keepAliveTime

线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用;

unit

keepAliveTime的单位;

workQueue

用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:

1)  ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;

2)  LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;

3)  SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;

       SynchronousQueue——直接提交策略,适用于CachedThreadPool。它将任务直接提交给线程而不保持它们。如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因为会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求最大的maxiumPoolSize以避免拒绝新提交的任务(正如CachedThreadPool这个参数的值为Integer.MAX_VALUE).当任务以超过队列所能处理的量、连续到达时,此策略允许线程具有增长的可能性。吞吐量较高。

       LinkedBlockingQueue——无价队列,适用于FixedThreadPool与SingleThreadExecutor。基于链表的阻塞队列,创建的线程池不会超过corePoolSizes(maximumPoolSize值与其一致),当线程正忙时,任务进入队列等待。按照FIFO原则对元素进行排序,吞吐量高于ArrayBlockingQueue。

4)  priorityBlockingQuene:具有优先级的无界阻塞队列;

threadFactory

创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。

handler

线程池的饱和策略,当阻塞队列满了且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
1)   AbortPolicy:直接抛出异常,默认策略;
2)   CallerRunsPolicy:用调用者所在的线程来执行任务;
3) DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4) DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略。

 
我们可以使用Executors这个工厂类生产各种类型的线程池,主要方法有三种:
1)Executors.newSingleThreadExecutor()——创建一个单线程的线程池,单线程串行工作,如果这个唯一的 线程因为异常终止,咋会有一个新的线程来替代它;
2)Executors.newFixedThreadPool()——创建一个固定大小的线程池,固定大小的线程池,如果设定的所有线程都在运行,新任务会在任务队列等待;
3)Executors.newCachedThreadPool()——创建一个可缓存的线程池,大小可伸缩的线程池。如果当前没有可用线程,则会创建一个新线程。在执行后缓存60s,60s后若这个线程依旧未被使用,则它将自动地被销毁。

二、向线程池中提交任务

当向线程池提交一个任务,线程池是如何处理这个任务呢?

1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程里的线程都在执行任务,则进入下一个流程;

2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务添加到这个工作队列里。如果工作队列满了,则进入下一个流程;

3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

ThreadPoolExecutor执行execute()方法的示意图,如下图所示:


以CachedThreadPool为例调用execute():


输出结果:


我们不仅使用execute()方法执行任务,还会使用submit()方法:

execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。

submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后返回,这时候有可能有任务没有执行完。

接下来让我们来看看execute()最为核心的方法!!!


这段代码看似简单,实则是复杂难懂

首先判空这个我就不需要多说废话了,接下来判定if(poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)),如果poolSize >= corePoolSize成立则将进入该条件中执行,当然也有可能不会成立,让我们一起来看看addIfUnderCorePoolSize(command)这一方法!!!



注:在addThread这个方法中,这里创建了一个Worker,并且poolSize++,然后将其放入workers的运行队列中,我们会发现这个Worker的定义也是一个Runnable,当我们调用t.start()方法,也就是线程的启动方法,实际上也就是调用了Worker的run方法。

我们可以看到当poolSize < corePoolSize并且这个线程池处于running状态时,就会通过addThread()方法创建一个新的线程并且将它运行起来,当创建成功addIfUnderCorePoolSize这个函数会返回true,反正则返回false;回到前面if(poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))这里,当poolSie >= CorePoolSize这一条件成立或者addIfUnderPoolSize(command)创建一个线程失败返回值为false时,才会进入该条件继续向下,继续看下面内容:



第一个if也就是当前状态为running时就会去执行workQueue.offer(command),这个workQueue实际就是一个BlockingQueue,offer就是在队列的尾部添加一个线程对象,添加成功则返回true,反之则返回false;所以说只有线程池在running状态才会往这个队列里面添加元素,反正则else if,很明显,else if是在做一个是否大于maximumPoolSize的判定,如果大于的话就做reject的操作。至于在第二个if是要在线程池运行状态不是Running或者poolSize == 0时候才会调用。为什么要再次判断runState是否为running状态,外面那层不是已经判断它是否等于Running了吗?这是因为在你进行第一条件判断时,因为时间差原因,这个状态可能会被改变而不再是Running状态,如果不是Running状态,那么在ensureQueuedTaskHandler方法中进行rejects操作。

总结:

关于线程池一些比较重要的类或接口:

(1)ExecutorService是真正的线程池接口,所以我们在通过Excutors创建各种线程时,一般采用如下代码:

ExecutorService threadPool = Executors.newXXX();

先声明一个线程池接口,运行时动态绑定具体的线程池对象,典型的接口的用法(虽然ExecutorService名字起的不像个接口……)。

(2)Executors是静态工厂的功能(虽然名字也不像个工厂……),生产各种类型线程池。

(3)Executor与Executors,Executor是线程池的顶级接口,但它只是一个执行线程的工具,真正的线程池接口是ExecutorService。

(4)AbstractExecutorService实现了ExecutorService接口,实现了其中大部分的方法(有没有实现的,所以被声明为Abstract)。

(5)ThreadPoolExecutor,继承了AbstractExecutorService,是ExecutorService的默认实现。

这五个类或接口实现了从线程池顶层接口到底层实现的整个架构。



原创粉丝点击