创建自己的多线程池类
本文通过ThreadGroup创建线程池类。
- 线程池介绍
- 自定义线程
- JDK自带线程池分析
线程池介绍
线程池就是预先创建一些工作线程,它们不断从工作队列中取出任务,然后完成任务。当工作线程执行完一个任务后,就会继续执行工作队列中的下一个任务。
线程池优点
- 减少了线程创建和销毁的次数,每个工作线程都可以一直被重用,能执行多个任务。
- 可以根据系统的承载能力,自由调整线程池中线程的数目。防止因为消耗过量资源而导致系统崩溃。
自定义线程
代码如下:
public class ThreadPool extends ThreadGroup{
private boolean isClosed = false;
private LinkedList workQueue;
private static int threadPoolID;
private int threadID;
public ThreadPool(int poolSize){
super("ThreadPool-"+(threadPoolID++));
setDaemon(true);
workQueue = new LinkedList<>();//创建工作队列
for(int i=0;i
ThreadPool的测试类:
public class ThreadPoolTest {
public static void main(String[] args) {
int numTask = 10;
int poolSize = 4;
ThreadPool threadPool = new ThreadPool(poolSize);
for(int i=0;i
代码分析
- 在ThreadPool类中定义了一个LinkedList类型的workQueue成员变量,它表示工作队列,用来存放线程池要执行的任务,每个任务都是Runnable实例。ThreadPool的测试方法只需调用execute()方法,就能向ThreadPool提交任务。
- 在ThreadPool类的构造方法中,会创建并启动若干工作线程,工作线程的数目由参数poolSize决定。WorkThread表示工作线程,它是ThreadPool类的内部类。工作线程从工作队列中获取一个任务,接着执行该任务,然后再从工作队列中取出下一个任务并执行它,如此反复。
- 在execute()方法中:先判断该线程池是否已经关闭,如果关闭抛出IllegalStateException异常。如果没有关闭的话,将任务加到workQueue中,然后在唤醒getTask()方法中获取任务的线程。
- getTask()方法实现:如果队列为空且线程池已关闭,则返回null。如果队列为空线程池没有关闭,则在此等待。如果队列中有任务则取出第一个将其返回。
- join()和close()方法都可用于关闭线程池。join()方法确保在关闭线程之前,工作线程把队列中的所有任务都执行完。而close()方法则情况队列,并中断所有工作线程。
测试类执行结果:
JDK自带线程池分析
测试JDK自带线程
代码如下:
public class ThreadPoolTest2 {
public static void main(String[] args) {
int poolSize = 4;
ExecutorService executorService = Executors.newFixedThreadPool(poolSize);
for(int i=0;i<10;i++) {
executorService.execute(createTask(i));
}
//executorService.shutdownNow(); //与自定义的线程池的close()方法类似
executorService.shutdown(); //与自定义的线程池的join()方法类似
}
private static Runnable createTask(final int taskId) {
return new Runnable() {
@Override
public void run() {
System.out.println("Task " + taskId + ":start");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + ":end");
}
};
}
}
执行结果如下:
在上面代码中通过ExecutorService executorService = Executors.newFixedThreadPool(poolSize);
创建线程池,该方法时创建一个固定数目的线程池。
进入newFixedThreadPool
方法内,内容如下:
这个方法这是个幌子,在进入
ThreadPoolExecutor
类中 的另一个构造方法点击
this
进入 查看各个参数表示什么意思:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:
* {@code corePoolSize < 0}
* {@code keepAliveTime < 0}
* {@code maximumPoolSize <= 0}
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
参数 corePoolSize :核心线程池线程数目。
参数 maximumPoolSize :最大线程池线程数。
参数 keepAliveTime :空闲时间,如果线程池中的线程数大于corePoolSize的话,则这些多出来的线程在空闲时间超过keepAliveTime时将会销毁。
参数 unit :keepAliveTime参数的单位。
参数 workQueue :工作队列。工作队列支持三种策略:
- 直接提交。工作队列的默认选项是
SynchronousQueue
一种阻塞队列。其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。 - 无界队列。使用无界队列(例如,不具有预定义容量的
LinkedBlockingQueue
)将导致在所有 corePoolSize 线程都忙于新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列; - 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如
ArrayBlockingQueue
)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致降低吞吐量。
参数 threadFactory :线程工厂。使用 ThreadFactory
创建新线程。如果没有另外说明,则在同一个 ThreadGroup
中一律使用 Executors.defaultThreadFactory()
创建线程,并且这些线程具有相同的 NORM_PRIORITY
优先级和非守护进程状态。
参数 handler :被拒绝的任务处理的handler。当 Executor
已经关闭,并且 Executor
将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable)
中提交的新任务将被 拒绝。在默认的 ThreadPoolExecutor.AbortPolicy
中,处理程序遭到拒绝将抛出运行时异常 RejectedExecutionException
。
类图
Executor
、ExecutorService
、Executors
三个类直接的关系:
总结:
类名 | 作用 |
---|---|
Executor | 线程池类,它的execute()方法用来执行Runnable类型的任务。 |
ExecutorService | 是Executor的子接口,申明了一些管理线程池的方法。比如shutdow() |
Executors | Executors包含一些静态方法,它主要负责创建各种类型的ExecutorService实例。 |
其实我们自己定义的线程池也是借鉴JDK自带的线程池的。在创建一个线程池的时候,都有这几个必要元素:线程池大小、线程存活时间、线程工厂、工作队列、任务拒绝处理方法。
在使用JDK的线程池中,我们只用到两个类,一个是Executors
和ExecutorService
这个是接口。其实还有一个接口很重要,因为它是execute()
方法的申明类,也是ExecutorService
的父接口,就是 Executor
。这三个类各司其职:
类名 | 作用 |
---|---|
Executor | 线程池类,它的execute()方法用来执行Runnable类型的任务。 |
ExecutorService | 是Executor的子接口,申明了一些管理线程池的方法。比如shutdow() |
Executors | Executors包含一些静态方法,它主要负责创建各种类型的ExecutorService实例。 |
欢迎关注微信公众号 在路上的coder
每天分享优秀的Java技术文章!
扫描二维码关注: