线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
java.uitl.concurrent.threadpoolexecutor
类是 executor 框架中最核心的一个类。
threadpoolexecutor 有四个构造方法,前三个都是基于第四个实现。第四个构造方法定义如下:
public threadpoolexecutor(int corepoolsize, int maximumpoolsize, long keepalivetime, timeunit unit, blockingqueue<runnable> workqueue, threadfactory threadfactory, rejectedexecutionhandler handler) {
corepoolsize
:线程池的基本线程数。这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了 prestartallcorethreads()或者 prestartcorethread()方法,从这 2 个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建 corepoolsize 个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为 0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到 corepoolsize 后,就会把到达的任务放到缓存队列当中。maximumpoolsize
:线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。keepalivetime
:线程活动保持时间。线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。workqueue
:任务队列。用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。threadfactory
:创建线程的工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
handler
:饱和策略。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是 abortpolicy,表示无法处理新任务时抛出异常。以下是 jdk1.5 提供的四种策略。
在 threadpoolexecutor 类中有几个非常重要的方法:
execute()
方法实际上是 executor 中声明的方法,在 threadpoolexecutor 进行了具体的实现,这个方法是 threadpoolexecutor 的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。submit()
方法是在 executorservice 中声明的方法,在 abstractexecutorservice 就已经有了具体的实现,在 threadpoolexecutor 中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和 execute()方法不同,它能够返回任务执行的结果,去看 submit()方法的实现,会发现它实际上还是调用的 execute()方法,只不过它利用了 future 来获取任务执行结果(future 相关内容将在下一篇讲述)。shutdown()
和 shutdownnow()
是用来关闭线程池的。我们可以使用 execute
提交任务,但是 execute
方法没有返回值,所以无法判断任务是否被线程池执行成功。
通过以下代码可知 execute
方法输入的任务是一个 runnable 实例。
threadspool.execute(new runnable() { @override public void run() { // todo auto-generated method stub } });
我们也可以使用 submit
方法来提交任务,它会返回一个 future
,那么我们可以通过这个 future
来判断任务是否执行成功。
通过 future
的 get
方法来获取返回值,get
方法会阻塞住直到任务完成。而使用 get(long timeout, timeunit unit)
方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。
future<object> future = executor.submit(harreturnvaluetask); try { object s = future.get(); } catch (interruptedexception e) { // 处理中断异常 } catch (executionexception e) { // 处理无法执行任务异常 } finally { // 关闭线程池 executor.shutdown(); }
我们可以通过调用线程池的 shutdown
或 shutdownnow
方法来关闭线程池,它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownnow 首先将线程池的状态设置成 stop,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown 只是将线程池的状态设置成 shutdown 状态,然后中断所有没有正在执行任务的线程。
只要调用了这两个关闭方法的其中一个,isshutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isterminaed 方法会返回 true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown 来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownnow。
jdk 中提供了几种具有代表性的线程池,这些线程池是基于 threadpoolexecutor
的定制化实现。
在实际使用线程池的场景中,我们往往不是直接使用 threadpoolexecutor
,而是使用 jdk 中提供的具有代表性的线程池实例。
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
这种类型的线程池特点是:
示例:
public class cachedthreadpooldemo { public static void main(string[] args) { executorservice executorservice = executors.newcachedthreadpool(); for (int i = 0; i < 10; i++) { final int index = i; try { thread.sleep(index * 1000); } catch (interruptedexception e) { e.printstacktrace(); } executorservice.execute(() -> system.out.println(thread.currentthread().getname() + " 执行,i = " + index)); } } }
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
fixedthreadpool 是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
示例:
public class fixedthreadpooldemo { public static void main(string[] args) { executorservice executorservice = executors.newfixedthreadpool(3); for (int i = 0; i < 10; i++) { final int index = i; executorservice.execute(() -> { try { system.out.println(thread.currentthread().getname() + " 执行,i = " + index); thread.sleep(2000); } catch (interruptedexception e) { e.printstacktrace(); } }); } } }
创建一个单线程化的 executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(fifo, lifo, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
示例:
public class singlethreadexecutordemo { public static void main(string[] args) { executorservice executorservice = executors.newsinglethreadexecutor(); for (int i = 0; i < 10; i++) { final int index = i; executorservice.execute(() -> { try { system.out.println(thread.currentthread().getname() + " 执行,i = " + index); thread.sleep(2000); } catch (interruptedexception e) { e.printstacktrace(); } }); } } }
创建一个线程池,可以安排任务在给定延迟后运行,或定期执行。
public class scheduledthreadpooldemo { private static void delay() { scheduledexecutorservice scheduledthreadpool = executors.newscheduledthreadpool(5); scheduledthreadpool.schedule(() -> system.out.println(thread.currentthread().getname() + " 延迟 3 秒"), 3, timeunit.seconds); } private static void cycle() { scheduledexecutorservice scheduledthreadpool = executors.newscheduledthreadpool(5); scheduledthreadpool.scheduleatfixedrate( () -> system.out.println(thread.currentthread().getname() + " 延迟 1 秒,每 3 秒执行一次"), 1, 3, timeunit.seconds); } public static void main(string[] args) { delay(); cycle(); } }
线程池的具体实现原理,大致从以下几个方面讲解:
// runstate is stored in the high-order bits private static final int running = -1 << count_bits; private static final int shutdown = 0 << count_bits; private static final int stop = 1 << count_bits; private static final int tidying = 2 << count_bits; private static final int terminated = 3 << count_bits; // packing and unpacking ctl private static int runstateof(int c) { return c & ~capacity; }
runstate 表示当前线程池的状态,它是一个 volatile 变量用来保证线程之间的可见性;
下面的几个 static final 变量表示 runstate 可能的几个取值。
当创建线程池后,初始时,线程池处于 running 状态;
running -> shutdown
如果调用了 shutdown()方法,则线程池处于 shutdown 状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕。
(running or shutdown) -> stop
如果调用了 shutdownnow()方法,则线程池处于 stop 状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务。
shutdown -> tidying
当线程池和队列都为空时,则线程池处于 tidying 状态。
stop -> tidying
当线程池为空时,则线程池处于 tidying 状态。
tidying -> terminated
当 terminated() 回调方法完成时,线程池处于 terminated 状态。
任务执行的核心方法是 execute()
方法。执行步骤如下:
public void execute(runnable command) { if (command == null) throw new nullpointerexception(); int c = ctl.get(); if (workercountof(c) < corepoolsize) { if (addworker(command, true)) return; c = ctl.get(); } if (isrunning(c) && workqueue.offer(command)) { int recheck = ctl.get(); if (! isrunning(recheck) && remove(command)) reject(command); else if (workercountof(recheck) == 0) addworker(null, false); } else if (!addworker(command, false)) reject(command); }
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
prestartcorethread():初始化一个核心线程; prestartallcorethreads():初始化所有核心线程
public boolean prestartcorethread() { return addifundercorepoolsize(null); //注意传进去的参数是null } public int prestartallcorethreads() { int n = 0; while (addifundercorepoolsize(null))//注意传进去的参数是null ++n; return n; }
在前面我们多次提到了任务缓存队列,即 workqueue,它用来存放等待执行的任务。
workqueue 的类型为 blockingqueue,通常可以取下面三种类型:
当线程池的任务缓存队列已满并且线程池中的线程数目达到 maximumpoolsize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略
threadpoolexecutor 提供了两个方法,用于线程池的关闭,分别是 shutdown()和 shutdownnow(),其中:
threadpoolexecutor 提供了动态调整线程池容量大小的方法:setcorepoolsize()和 setmaximumpoolsize(),
当上述参数从小变大时,threadpoolexecutor 进行线程赋值,还可能立即创建新的线程来执行任务。
免费java资料需要自己领取,涵盖了java、redis、mongodb、mysql、zookeeper、spring cloud、dubbo高并发分布式等教程,一共30g。
传送门:https://mp.weixin.qq.com/s/jzddfh-7ynudmkjt0irl8q
如对本文有疑问, 点击进行留言回复!!
[JVM学习之路]一、初识JVM,了解其结构、模型及生命周期
【JAVA并发编程】LinkedBlockingQueue原理
网友评论