Java 多线程案例之线程池
🎵1.线程池概述
🎶1.1 什么是线程池
线程池和字符串常量池一样,都是为了提高程序运行效率而提出的效率,程序中每创建一个线程就会把该线程加载到一个“池子”中去,其实这个池子就是 List,当程序下次需要调用该线程的时候,可以直接从线程池中去取,而不用花费更大的力气去重新创建和销毁线程,从而使程序的运行效率提高,线程池也是管理线程的方式之一。
🌳那为什么从线程池中“拿”线程会比直接创建线程要更加高效呢?
因为使用线程池调度线程是在用户态实现的,而线程的创建是基于内核态实现的。那为什么说用户态比内核态更加高效呢?因为你将任务交给内核态时,内核态不仅仅只去完成你交给它的任务,大概率还会伴随完成其他的任务,而你将任务交给用户态时,用户态只去完成你所交代的任务,所以综上所述,用户态效率更高。
🎶1.2Java 线程池标准类
java 也提供了相关行程池的标准类ThreadPoolExecutor
,也被称作多线程执行器,该类里面的线程包括两类,一类是核心线程,另一类是非核心线程,当核心线程全部跑满了还不能满足程序运行的需求,就会启用非核心线程,直到任务量少了,慢慢地,非核心线程也就退役了,通俗一点核心线程就相当于公司里面的正式工,非核心线程相当于临时工,当公司人手不够的时候就会请临时工来助力工作,当员工富余了,公司就会将临时工辞退。
jdk8 中,提供了 4 个构造方法,我主要介绍参数最多的那一个构造方法,其他 3 个构造方法都是基于此构造方法减少了参数,所以搞懂最多参数的构造方法,其他构造方法也就明白了。
corePoolSize
表示核心线程数。maximumPoolSize
表示最大线程数,就是核心线程数与非核心线程数之和。keepAliveTime
非核心线程最长等待新任务的时,就是非核心线程的最长摸鱼时间,超过此时间,该线程就会被停用。unit
时间单位。workQueue
任务队列,通过submit
方法将任务注册到该队列中。threadFactory
线程工厂,线程创建的方案。handler
拒绝策略,由于达到线程边界和队列容量而阻止执行时使用的处理策略。
其他几个构造方法:
🌳那核心线程数最合适值是多少呢?假设 CPU 有 N 核心,最适核心线程数是 N?是 2N?是 1.5N?只要你能够说出一个具体的数,那就错了,最适的核心线程数要视情况而定,没有一个绝对的标准的值。
在具体使用线程池时,往往使用的是Executor
,因为 Executor
是 ThreadPoolExecutor
所实现的一个接口,由于标准库中的线程池使用较复杂,对于ThreadPoolExecutor
类中的方法我们就不介绍了,最重要的一个方法是submit
方法,这个方法能够将任务交给线程池去执行,接下来我们来理一理线程池最基本的工作原理,我们来尝试实现一个简单的线程池。
🎵2.线程池的实现
🎶2.1 线程池的基本工作原理
线程池是通过管理一系列的线程来执行程序员所传入的任务,这些任务被放在线程池对象中的一个阻塞队列中,然后线程池会调度线程来执行这些任务,优先调度核心线程(核心线程会在线程池对象构造时全部创建),如果核心线程不够用了,就会创建非核心线程来帮忙处理任务,当非核心线程一定的时间没有收到新任务时,非核心线程就会被销毁,我们实现线程池的目的是加深对线程池的理解,所以实现的过程中就不去实现非核心线程了,线程池里面的线程全部以核心线程的形式实现。
🌳我们需要实现一个线程池,根据以上的原理需要准备:
任务,可以使用 Runnable。
组织任务的数据结构,可以使用阻塞对列。
工作线程(核心线程)的实现。
组织线程的数据结构,可以使用 List。
新增任务的方法
submit
。
🎶2.2 线程池的简单实现
关于任务和任务的组织就不用多说了,直接使用Runnable
和阻塞队列BlockingQueue<Runnable>
就可以了,重点说一下工作线程如何描述的,工作线程中需要有一个阻塞队列引用来获取我们存任务的那一个阻塞队列对象,然后重写run
方法通过循环不断的获取任务执行任务。
然后根据传入的核心线程数来创建并启动工作线程,将这些线程放入顺序表或链表中,便于管理。
最后就是创建一个submit
方法用来给用户或程序员派发任务到阻塞队列,这样线程池中的线程就会去执行我们所传入的任务了。
🌳实现代码:
🌳我们来测试一下我们所实现的线程池:
🌳运行结果:
好了,你知道线程池的工作原理了吗?
下期预告:常见的锁策略以及 CAS 与 ABA 相关问题。
版权声明: 本文为 InfoQ 作者【未见花闻】的原创文章。
原文链接:【http://xie.infoq.cn/article/27fa4a8536c2854b4e7507942】。文章转载请联系作者。
评论