博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
线程池的简介、应用和手动实现线程池
阅读量:6626 次
发布时间:2019-06-25

本文共 5960 字,大约阅读时间需要 19 分钟。

hot3.png

一 简介

java中接触到"池"这个概念的地方有不少。最开始是常量池、数据库连接池、线程池... 对于某些发散思维很强或者善于举例的人来说,可能会想方设法将java的某些概念和生活中的某些事物进行比拟,好加深理解。惭愧的是,关于生活中的"池",我最先想到的是水池、化粪池(我是农村银)... ... 

再后来,有了一些经验之后。才慢慢体会到。编程语言中的"池",侧重强调的不是作为容器的存储功能,而是容器中的元素能够重复利用的功能。

java中有线程池的概念,jdk也有相应的实现类。本文并非探讨JDK源码,大失所望者请勿浪费时间往下看。我一向不喜欢看别人的代码。不是狂妄。而是不希望自己的想法被别人的思路绑架。至少要等我自己的代码出现了很大的问题,严重碰壁的时候,才去学习借鉴别人的想法。

1 为什么要使用线程池呢?

答:线程池可以提高程序的性能。

2 使用了多线程本身不就是可以提高性能了吗?为什么线程池可以提高性能?

答前半个问题:多线程不一定提高性能,甚至反而降低了性能。多线程环境中,如果每个线程run方法中运行的代码耗时比较少,少于创建、启动该线程所消耗的时间。使用多线程就是一种浪费

场景一 :创建线程、开启线程、线程开始执行这段耗时  远远大于 线程的运行时间。

单线程搜索文件和多线程搜索文件性能对比:

单线程:

/** * 单线程搜索文件。 * @author Administrator * */public class SingleThreadSearchFile {	public static void main(String[] args) {		long t1 = System.currentTimeMillis();		//在 G:\\代码库备份  这个目录中搜索包含"笔记"的文件夹或文件。并打印出来。		searchFile(new File("G:\\代码库备份"), "笔记"); 		long t2 = System.currentTimeMillis();		System.out.println("耗时:"+(t2-t1)+"ms"); 		 	}	public static void searchFile(File file,String keyword){		if(file.isDirectory()){			 File []files = file.listFiles();			 if(files!=null){				 isKeyWordContained(file, keyword);//搜索业务。				 for(File f : files){					 searchFile(f, keyword);				 }			 }		}else{ 			isKeyWordContained(file, keyword);//搜索业务。		}	}	public static void isKeyWordContained(File file,String keyword){		 int index = file.getName().indexOf(keyword);		 if(index !=-1){			 System.out.println(file.getAbsolutePath());		 } 	}}

多线程:

/** *  * @author Administrator * */public class MultiThreadSearchFile {	public static void main(String[] args) throws InterruptedException {		long t1 = System.currentTimeMillis();		//在G:\\代码库备份  这个目录中搜索包含"笔记"的文件夹或文件。并打印出来。		searchFile(new File("G:\\代码库备份"), "笔记"); 		long t2 = System.currentTimeMillis();		System.out.println("耗时:"+(t2-t1)+"ms"); 	}	public static void searchFile(File file,String keyword){		if(file.isDirectory()){			 File []files = file.listFiles();			 if(files!=null){				 isKeyWordContained(file, keyword);				 //取出目录名,判断是否包含关键字。				 for(File f : files){					 searchFile(f, keyword);				 }			 }		}else{//是否文件。			isKeyWordContained(file, keyword);		}	}	//被调用一次,则开一个线程来查找。所谓的查找其实仅仅是获取文件名,与关键字进行简单的比对。	public static void isKeyWordContained(File file,String keyword){		 Thread t = new Thread(){			 public void run(){				 int index = file.getName().indexOf(keyword);				 if(index !=-1){					 System.out.println(getName()+"  "+file.getAbsolutePath());				 }			 }		 };		 t.start();		try { 		  t.join();		}catch (InterruptedException e) {  }		 	}}

对比结果:相同的目录查找相同的结果。单线程60ms多线程220--400ms变动。单线程完胜。原因很简单:我们虽然使用了多线程,但是多线程里面要做的事情仅仅是几行简单的不耗时的代码。多线程同运行run方法带来的优势已经被创建线程、开启线程所需要的时间给抵消掉了。

场景二 创建线程、开启线程、线程开始执行这段耗时  远远 小于 线程的运行时间

多线程的应用场景在于,执行那些需要耗时的操作。例如,使用多线程 + Socket实现的Http服务器。每个连接都开启一个线程来处理请求响应。由于存在网络延时。因此多线程的优势体现出来。

再如,将上面的案例需求修改如下:搜索某个文件夹中,文件内容 包含指定关键字的.txt, .java , .js文件。

单线程的业务代码改为:

public static void isKeyWordContained(File file,String keyword){		  if(file.getName().endsWith(".java") || file.getName().endsWith(".txt")||				  file.getName().endsWith(".js")){			  //创建流对文件内容进行读取,并且判断。		  }	}

多线程业务代码改为:

public static void isKeyWordContained(File file,String keyword){		 Thread t = new Thread(){			 public void run(){				if(file.getName().endsWith(".java") || file.getName().endsWith(".txt")||				  file.getName().endsWith(".js")){			       //创建流对文件内容进行读取,并且判断。		        }			 }		 };		 t.start();		try { 		  t.join();		}catch (InterruptedException e) {  }	}

由于使用IO读取文件是个相对耗时的操作,此时多线程的优势展示出来了。

答后半个问题:经过上述分析,已经得出了个结论。如果不使用线程池,由于创建线程、开启线程过程中,JVM要为每个线程分配内存空间,相对耗时,所以不一定提高性能。如果能将线程放入一个池里,不需要每次都开启和运行。就在一定程度上提高性能

二 想法

如果实现线程池?回想线程池的作用:

①首先得是个容器,容器里面装有线程。可以是Thread[]或者List<Thread>

其次容器中的线程还得重复利用,意味着线程不死,如何不死?最粗暴的方法是run方法中死循环(一定条件下可调出)

矛盾:那如何让线程执行自定义的代码,自定义的代码放在哪里?

答案是:任务。

这里引出任务的概念,这里的任务,是我们自定义个一个接口。

public interface  MyTask {	//被调用。被线程池中的线程调用。	public  void execute()throws Exception;}

我们的代码放在哪里呢?就放在子类的execute方法里。

即,我们创建一个实现类,把代码放在execute方法中。然后创建该类的对象,一个类就表示一个任务。

好啦,到这里,整理一下我们线程池的所有流程:

应该要有一个线程池管理类,里面包含着任务列表,MyTask[]或者List<MyTask>,也可以用阻塞队列。

也包含了线程列表。并且线程可以运行状态。

使用线程池时,只需要创建任务对象,把代码写好,把对象丢进线程池。

理想的编程方式如下:

//创建线程池。//创建任务1 ,并加入线程池。//创建任务2,并加入线程池//创建任务3,并加入线程池...       伪代码如下:       //1 创建线程池。指定池里有5个正在运行的线程(活跃)		MyThreadPools pools  = new MyThreadPools(5);		//2 创建任务列表。并加入线程池。		Random r = new Random();		for(int i=0;i<20;i++){			MyTask t = new MyTask() {				@Override				public void execute() throws Exception{					 System.out.println("产生随机数:"+r.nextInt());				}			};			pools.submit(t);		}

三 代码

完整代码,2个类和一个接口。

MyTask.java代码如下

public interface  MyTask {	//被调用。被线程池中的线程调用。	public  void execute()throws Exception;}

MyThreadPools.java代码如下

/** * 线程池。管理类。 * @author Administrator * */public class MyThreadPools {    //实际应用中,可将以下两个换成阻塞队列。这样就避免在run方法中手动使用synchronized关键字来同步。	private  List
threadList = new ArrayList
();//线程池的线程列表。 private List
mytasklist = new LinkedList
();//任务列表。 //num表示线程池中线程的总数。 public MyThreadPools(int num){ for(int i=0;i
0)task = mytasklist.remove(0); else break;//一定条件下推出。也可以不退出。 } if(task!=null ){ System.out.println(getName()+"线程执行任务"); try { task.execute(); } catch (Exception e) { System.out.println("有任务有异常。执行失败。"); } System.out.println(getName()+"线程执行完毕"); } } System.out.println("线程"+getName()+"退出"); } }}

TestMain.java代码如下

public class TestMain {	public static void main(String[] args) {		//1 创建线程池。		MyThreadPools pools  = new MyThreadPools(5);		//2 创建任务列表。并加入线程池。		Random r = new Random();		for(int i=0;i<20;i++){			MyTask t = new MyTask() {				@Override				public void execute() throws Exception{					 System.out.println("产生随机数:"+r.nextInt());				}			};			pools.submit(t);		}	}}

四 优化

这就完了吗?肯定不会。我们这里的线程池,仅仅实现的是固定数目的线程池,并且线程没事干的时候就退出,这个机制有待商榷。更多的时候,我们需要根据任务的数量来决定线程中最小活跃的线程数量、最大线程数量等。因此还可以做得更加完善。但至少以上已经实现了线程池的根本模型。相信再开发自己的线程池,也就不是难事了。

有一点要说明:JDK提供的线程池,并没有新定义一个类来表示任务。它使用了Runnable接口作为任务。因此,我们的自定义逻辑就写在run方法中。线程池中的线程会调用任务的run方法。

085257_vYcY_3866423.png

 

 

 

转载于:https://my.oschina.net/lightled/blog/1821547

你可能感兴趣的文章
【云计算的1024种玩法】使用 NAS 文件储存低价获得好磁盘性能
查看>>
H.264学习笔记之一(层次结构,NAL,SPS)
查看>>
Radware:IP欺诈等让网络攻击难以防范
查看>>
基于Token认证的WebSocket连接
查看>>
【Solidity】2.合约的结构体 - 深入理解Solidity
查看>>
《Drupal实战》——2.6 小结
查看>>
《C语言及程序设计》实践参考——二分法解方程
查看>>
java thread中的wait()和notify()
查看>>
2016最新搜索引擎优化(SEO)重点要素
查看>>
当Web访问性能出现问题,如何深探?
查看>>
【IOS-COCOS2D-X 游戏开发之二】【必看篇】总结阐述COCOS2D-X与COCOS2D-IPHONE区别;
查看>>
eoLinker-API_Shop_通讯服务类API调用的代码示例合集:短信服务、手机号归属地查询、电信基站查询等...
查看>>
前端面试回忆录 - 滴滴篇 - 凉面
查看>>
jxl导入Excel 切割List 并使用MyBatis批量插入数据库
查看>>
小程序开发总结
查看>>
Tomcat监听器设计思路
查看>>
管理ORACLE实例
查看>>
Confluence 6 MySQL 数据库设置准备
查看>>
Ruby 中 0/0.0 = NaN
查看>>
JEESNS数据库表设计结构
查看>>