并发编程之手写一个简单的线程池

并发编程之手写一个简单的线程池

前言:

有些人可能对线程池比较陌生,并且更不熟悉线程池的工作原理。所以他们在使用多线程的时候,往往都是通过直接new Thread来实现多线程。但是往往良好多线程的设计大多都是使用线程池去实现,今天主要是跟大家分享如何自己实现一个简单的线程池,帮助理解线程池的工作的原理,以及手动实现的这个线程池存在哪些缺点不足,最后分析JDK源码中的线程池是如何设计来解决这些缺点和不足。

一,为什么要使用线程池

(1)降低资源的消耗。降低线程创建和销毁的资源消耗;

(2)提高响应速度:线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间

(3)提高线程的可管理性。

二,手写一个线程池

1、在手写一个线程池之前,我们应该简单的考虑一下,这个线程的数据结构

(1)线程池中运行线程的个数

(2)线程池中如何保存未处理的任务

(3)线程池中的执行任务和清除任务的方法

2、直接上代码吧

MySelfThreadPool .java

package com.concurrent.pool;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class MySelfThreadPool {
	//默认线程池中的线程的数量
	private static final int WORK_NUM = 5;
	//默认处理任务的数量
	private static final int TASK_NUM = 100;
	
	private int workNum;//线程数量
	
	private int taskNum;//任务数量
	
	private final Set<WorkThread> workThreads;//保存线程的集合
	
	private final BlockingQueue<Runnable> taskQueue;//阻塞有序队列存放任务
	
	
	public MySelfThreadPool() {
		this(WORK_NUM, TASK_NUM);
	}


	public MySelfThreadPool(int workNum, int taskNum) {
		if (workNum <= 0) workNum = WORK_NUM;
		if (taskNum <= 0) taskNum = TASK_NUM;
		taskQueue = new ArrayBlockingQueue<>(taskNum);
		this.workNum = workNum;
		this.taskNum = taskNum;
		workThreads = new HashSet<>();
		//启动一定数量的线程数,从队列中获取任务处理
		for (int i=0;i<workNum;i++) {
			WorkThread workThread = new WorkThread("thead_"+i);
			workThread.start();
			workThreads.add(workThread);
		}
	}
	
	/**
	 * 线程池执行任务的方法,其实就是往BlockingQueue中添加元素
	 * @param task
	 */
	public void execute(Runnable task) {
		try {
			taskQueue.put(task);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void destroy() {
		System.out.println("ready close thread pool...");
		if (workThreads == null || workThreads.isEmpty()) return ;
		for (WorkThread workThread : workThreads) {
			workThread.stopWork();
			workThread = null;//help gc
		}
		workThreads.clear();
	}
	
	/**
	 * 线程池中的工作线程,直接从BlockingQueue中获取任务
	 * 然后执行任务而已
	 * blockQueue为阻塞队列
	 *
	 */
	private class WorkThread extends Thread{
		public WorkThread(String name) {
			super();
			setName(name);
		}
		
		@Override
		public void run() {
			while (!interrupted()) {
				try {
					Runnable runnable = taskQueue.take();//获取任务
					if (runnable !=null) {
						System.out.println(getName()+" ready execute:"+runnable.toString());
						runnable.run();//执行任务
					}
					runnable = null;//help gc
				} catch (Exception e) {
					interrupt();
					e.printStackTrace();
				}
			}
		}
		
		
		public void stopWork() {
			interrupt();
		}
	}
	
	
}

到上面,我们就已经实现了一个简单的线程池了,当然功能比较简单,但是对我们理解线程池的工作原理还是很有帮助,下面我们来看看测试程序

TestMySelfThreadPool .java

package com.concurrent.pool;

public class TestMySelfThreadPool {
	
	private static final int TASK_NUM = 50;//任务的个数

	public static void main(String[] args) {
		MySelfThreadPool myPool = new MySelfThreadPool(3,50);
		for (int i=0;i<TASK_NUM;i++) {
			myPool.execute(new MyTask("task_"+i));
		}
		
	}
	
	static class MyTask implements Runnable{
		
		private String name;
		public MyTask(String name) {
			this.name = name;
		}
		
		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}


		@Override
		public void run() {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("task :"+name+" end...");
			
		}
		
		@Override
		public String toString() {
			// TODO Auto-generated method stub
			return "name = "+name;
		}
	}
}

3, 程序运行的部分截图:

并发编程之手写一个简单的线程池

上面实现的线程池其实非常简单,就是客户端往线程池中的队列中添加任务,然后线程池中的线程一直从队列中去拿任务,拿到就执行,拿不到就一直阻塞。测试程序中就是创建多个任务,然后往线程池中扔。

虽然上面实现的简单的线程池能够实现基本功能,但是还是存在很多的不足

(1)线程池中的线程的个数,不能随着任务进行自动调整,这可能导致线程的浪费,或者导致提交的任务一直无法执行,导致应用崩溃

(2)当无法处理任务的时候,没有很好的方式一直去处理,而是让它一直阻塞

针对上面的问题,我们来看看jdk源码中的线程池是如何去处理的

三,ThreadPoolExecutor原理分析

先看下ThraedPoolExecutor的构造方法(参数最多的那个)

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

1,参数解析:

int corePoolSize:线程池中核心线程数,< corePoolSize,就会创建新线程,= corePoolSize,这个任务就会保存到BlockingQueue,如果调用prestartAllCoreThreads()方法就会一次性的启动corePoolSize个数的线程。

int maximumPoolSize:允许的最大线程数,BlockingQueue也满了,< maximumPoolSize时候就会再次创建新的线程

long keepAliveTime:线程空闲下来后,存活的时间,这个参数只在> corePoolSize才有用

TimeUnit unit:存活时间的单位值(秒、毫秒…)

BlockingQueue<Runnable> workQueue:保存任务的阻塞队列

ThreadFactory threadFactory:创建线程的工厂,给新建的线程赋予名字

RejectedExecutionHandler handler:饱和策略

AbortPolicy:直接抛出异常,默认;

CallerRunsPolicy:用调用者所在的线程来执行任务

DiscardOldestPolicy:丢弃阻塞队列里最老的任务,队列里最靠前的任务

DiscardPolicy:当前任务直接丢弃

实现自己的饱和策略,实现RejectedExecutionHandler接口即可

2,图解

看了上面的参数解析,其实基本上已经了解了它的处理的逻辑了,下面用一个图来解释一下

并发编程之手写一个简单的线程池

(1)当线程池中的线程个数<corePool,每次进来新的任务,就启动一个线程去处理这个任务

(2)当线程池中的线程个数>=corePool,这时每次进来的新的任务,就会加入到队列中。

(3)当线程池中的线程个数>=corePool但是<maxiMumPool,并且队列也满了(有界队列),这个时候再来新的任务,就会继续创建新的线程去处理。这个时候创建的线程,当线程空闲下来的时候到keepAliveTime的时间就会销毁(线程资源宝贵啊)

(4)当线程池中的线程数>=maximumPool时,这个时候再来新的任务,就可以选择拒绝的机制

3、代码解析

接下来看看代码验证一下上面的描述把,看下面这一段就行了

 并发编程之手写一个简单的线程池

好了,就分享到这里把,欢迎指正!

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111189.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号