在手写简单的线程池之前首先需要清楚为什么要用线程池,线程池存在的意义是什么?
我们用火影忍者里面鸣人的多重影分身术来理解,鸣人的多重影分身术可以出现成千上万的分身,每出现一个分身都需要消耗一定的查克拉,这时候给鸣人分配一个任务,篮子里面有100个鸡蛋需要鸣人吃完,假设鸣人的查克拉总量是100,这时候鸣人放出100个影分身开始吃鸡蛋,最后鸡蛋吃完了,但鸣人的查克拉也消耗完了,目的虽然达成了但是也消耗完了自身的查克拉,每个分身在吃完鸡蛋后就自己消失了,但是每个鸡蛋大小各异,每个分身吃的速度也各不相同,有快的也有慢的,那有没有办法既能将鸡蛋全都吃完还让鸣人有更多的查克拉呢?
这时候聪明的佐助给鸣人出了个主意让他只分身50个,让吃完的继续吃剩下的鸡蛋,鸣人照着做了,查克拉量不仅还剩下50,鸡蛋也同样吃光了。
根据这个任务肯定会有其他忍者说那50个肯定没有100个吃得快,这个情况肯定是存在的,因为毕竟人多吗?可是综合考虑你的查克拉量本身就不大为何要让自己挂掉完成任务呢?
所以这就跟我们做系统一样,任何系统不应该以高负荷占用资源去完成自己的任务,这样只会让系统崩溃的更快,从鸣人就可以衍生到计算机里面,查克拉相当于有限的资源,吃鸡蛋相当于很多用户的操作或者任务,分身相当于很多个线程在做这些任务,这是一个资源与时间的取舍问题,当然第一要务不能宕机,所以我们选择时间,但是吃鸡蛋的人少未必就吃得慢,吃的快的可以吃得更多这样资源得以最大化利用从而实现任务完成,系统永存,在衍生到线程池就更容易理解了,线程池的目的就是在不创建新线程的情况下让已经执行完任务的线程再去执行新的任务,从而达到资源的有效利用而不是无节制的浪费资源。
任何技术原理可以不知道怎么用代码写出来,但是必须知道这个技术存在的目的和技术思想以及为什么要出现这个技术,与其说学编程是学技术倒不如说是学思想。
目录
一、编写线程池代码,线程池主线程代码,测试代码
import java.util.List; import java.util.Vector; /** * @author xiaomianyang * @description * @date 2020-02-08 14:31 */ public class ThreadPool { private static ThreadPool instance=null; private List<PThread> idleThreads; private int threadCounter; private boolean isShutdown=false; private ThreadPool(){ this.idleThreads = new Vector<>(5); threadCounter = 0; } /** * @description 获取创建的线程数量 * @author xiaomianyang * @date 2020-02-15 16:14 * @param [] * @return int */ public int getCreatedThreadsCount(){ return threadCounter; } /** * @description 初始化线程池 * @author xiaomianyang * @date 2020-02-15 16:15 * @param [] * @return ThreadPool */ public synchronized static ThreadPool getInstance(){ if(instance==null){ instance = new ThreadPool(); } return instance; } /** * @description 重新洗牌 如果线程池不被关闭 将空闲线程添加到线程池中 * @author xiaomianyang * @date 2020-02-15 16:16 * @param [repoolingThread] * @return void */ protected synchronized void repool(PThread repoolingThread){ if(!isShutdown){ idleThreads.add(repoolingThread); }else{ repoolingThread.shutDown(); } } /** * @description 关闭线程池 对所有空闲线程进行依次循环关闭 * @author xiaomianyang * @date 2020-02-15 16:17 * @param [] * @return void */ public synchronized void shutDown(){ isShutdown=true; for(int threadIndex=0;threadIndex<idleThreads.size();threadIndex++){ PThread idleThread=(PThread)idleThreads.get(threadIndex); idleThread.shutDown(); } } /** * @description 启动线程池 如果有空闲线程就使用空闲线程执行目标任务 如果没有则重新创建 * @author xiaomianyang * @date 2020-02-15 16:17 * @param [target] * @return void */ public synchronized void start(Runnable target){ PThread thread=null; if(idleThreads.size()>0){ int lastIndex = idleThreads.size()-1; thread = (PThread)idleThreads.get(lastIndex); idleThreads.remove(lastIndex); thread.setTarget(target); }else{ threadCounter++; thread=new PThread(target,"PThread #"+threadCounter,this); thread.start(); } } public static void main(String[] args) { // 采用传统方式创建10万个线程 new Thread(()->{ long start=System.currentTimeMillis(); for(int i=0;i<100000;i++){ new Thread(new MyThread("testNoThreadPool"+Integer.toString(i))).start(); } System.out.println("不用线程池创建花费时间:"+(System.currentTimeMillis()-start)/1000+"秒"); }).start(); // 采用线程池的方式创建10万个线程 new Thread(()-> { long start=System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { ThreadPool.getInstance().start(new MyThread("testThreadPool" + Integer.toString(i))); } System.out.println("线程总数量:" + ThreadPool.getInstance().getCreatedThreadsCount()); System.out.println("线程池创建花费时间:"+(System.currentTimeMillis() - start) / 1000 + "秒"); }).start(); } } /** * @description 一个永不关闭的线程用于执行任务 * @author xiaomianyang * @date 2020-02-15 16:18 * @param * @return */ class PThread extends Thread{ private ThreadPool pool; private Runnable target; private boolean isShutdown=false; private boolean isIdle = false; public PThread(Runnable target,String name,ThreadPool pool){ super(name); this.pool=pool; this.target=target; } public Runnable getTarget(){ return target; } public boolean isIdle(){ return isIdle; } /** * @description 这里相当于线程池中的主线程 在不执行关闭的情况下 一直运行子线程的任务 * @author xiaomianyang * @date 2020-02-15 16:19 * @param [] * @return void */ public void run(){ while(!isShutdown){ isIdle=false; if(target!=null){ target.run(); } isIdle = true; try{ pool.repool(this); synchronized (this){ wait(); } }catch (InterruptedException e){ e.printStackTrace(); } isIdle = false; } } /** * @description 目标线程切换 * @author xiaomianyang * @date 2020-02-15 16:19 * @param [newTarget] * @return void */ public synchronized void setTarget(Runnable newTarget){ target=newTarget; notifyAll(); } /** * @description 关闭线程 * @author xiaomianyang * @date 2020-02-15 16:19 * @param [] * @return void */ public synchronized void shutDown(){ isShutdown = true; notifyAll(); } } class MyThread implements Runnable{ protected String name; public MyThread(String name) { this.name = name; } @Override public void run() { try{ Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
二、线程池详解
这里的ThreadPool类就是一个线程池子,他的作用就是创建和管理线程池,MyThread就是线程要执行的任务,PThread就是主线程,这个线程在不调用关闭方法前是一直在运行的,它的作用就相当于鸣人去发现那些已经吃完鸡蛋的再去吃新的鸡蛋不让他们偷懒,main方法就是用来测试传统创建线程和利用线程池创建线程花费的时间比。
这里的流程就是循环创建十万个线程,线程池每创建一个线程就会执行,如果该线程执行完成则将该线程放入到空闲线程集合中,当空闲线程集合大于0的时候就不再创建新的线程而使用空闲线程执行当前任务,这样就可以很好的控制系统资源开销。
三、运行结果如下
通过结果可以看出使用线程池要比传统方式时间减少了将近一半,这里由于两个方法一起执行的,利用线程池花费了21秒,如果将main方法中的第一个线程注释掉,会发现只需要3秒钟,同样的方式将第二个下称注释掉,传统的方式执行需要花费19秒,这个差距可想而知,可见线程池的好处和优势。
线程总数量:33600 线程池创建花费时间:21秒 不用线程池创建花费时间:43秒
四、使用线程池的好处
使用线程池可以减少线程创建和销毁对于资源的开销,每次创建线程和销毁线程都要花费时间和资源,而且CPU时间片在将近10万个线程之间切换效率会非常低,所以我们采用线程池就是以最少的资源开销完成更多的任务
五、线程池应用场景
那么线程池可以用在什么地方呢?一般的系统实际上都很难用到线程池,只有需要很多定时任务执行的系统才会用到,有些是系统本身任务有些是用户触发的任务,拿用户触发来说,比如用户上传照片到服务器,那么有很多用户都在上传我们就需要很多的线程来处理,这时候可以利用一定数量的线程来处理,用线程池来管理,防止因为用户量的剧增导致服务器线程数量增大导致服务器崩溃,这些线程可以供很多的用户调用,还有很多其他的场景,比如用户从服务器下载资源或者导出多个业务数据,我们就需要线程池在后端从各个业务模块调取数据来处理,也就是在任务量不确定而且会很多的情况下使用线程池是最佳选择。