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

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

前言:

有些人可能对线程池比较陌生,并且更不熟悉线程池的工作原理。所以他们在使用多线程的时候,往往都是通过直接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、代码解析

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

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

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

 

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • tomcat部署war包,jar包

    tomcat部署war包,jar包在tomcat根目录下部署war包,通过http://ip:port的方式访问网上说的方法很多,这里记录一下自己的经过实践检验的一种方法。1、先去tomcat/conf/server.xml里有一个HOST标签,有个参数appBase,表示你的应用应该部署在什么位置。例如下面写的是webapps,那就在webapps/ROOT/路径下解压你的war包。如果appBase=XXX,那么就…

    2022年6月4日
    21
  • jQuery实现手机竖直手风琴效果

    效果:http://hovertree.com/texiao/jquery/65/效果图:手机扫描二维码查看效果:代码:转自:http://hovertree.com/h/bjaf/vk9yjrp

    2021年12月23日
    46
  • Windows Socket UDP协议(服务器与客户端)

    Windows Socket UDP协议(服务器与客户端)

    2021年8月30日
    72
  • 各省DNS列表[通俗易懂]

    各省DNS列表[通俗易懂]各省DNS列表省份/城市DNS名称DNSIPADDRESS==========================================================香港ns1.netvigator.com205.252.144.228澳门vassun2.macau.ctm.net202.175.3.8深圳ns.shenzhen.gd.cn202.96.

    2022年6月11日
    30
  • 程序员必备:变量命名神器 CODELF

    程序员必备:变量命名神器 CODELF大部分开发者都或多或少遇到过变量命名的烦恼,如果命名不规范,不仅会影响开发的效率,而且对后面维护的同学来说也是一个不小的挑战。那么接下来就给大家介绍个命名神器

    2022年5月4日
    1.3K
  • 八图h5怎么发给朋友_最酷h5场景

    八图h5怎么发给朋友_最酷h5场景八图h5提供海量h5模板、邀请函、企业宣传、婚礼、会议会展、求职招聘等场景模板,在线一键生成微信邀请函、八图h5全网邀请函在线制作最优惠平台。  为什么场景化h5影响,能在朋友圈火起来?其实纵观这些能引爆朋友圈的H5,无非有以下特点!  1、会讲故事  h5场景说到底就是个新的页面技术,如何玩转h5,主要还是营销和创意,创意其实就是要会说故事,能把故事说好。h5里面整体故事有…

    2025年8月2日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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