当前位置: 首页 > 科技观察

10分钟handjobJava线程池,yyds!!

时间:2023-03-12 09:15:42 科技观察

最近有很多小伙伴私信我说:看了我在【精通高并发系列】一文写的深度剖析线程池源码的文章,但是还是不懂实现线程池的原理。问我能不能手写一个简单的线程池,帮助读者深入理解线程池的原理。这不,我熬夜写这篇文章。在【精通高并发系列】一文中,我们深入剖析了线程池的源码,从源码层面深入剖析了线程池的实现原理。其实源码是原理最直接的体现,理解源码对于深入理解原理有很大的帮助。但是很多朋友在看源码的时候,总觉得源码太枯燥,看不懂。今天,就让我们一起花10分钟玩一个极简版的Java线程池,让小伙伴们更好的理解线程池的核心原理。本文的整体结构如下。Java线程池的核心原理看过Java线程池源码的人都知道,Java线程池中的核心类是ThreadPoolExecutor,而ThreadPoolExecutor类中的核心构造方法是7个参数的构造方法,如下所示。publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler)的参数含义如下。corePoolSize:线程池常驻核心线程数。maximumPoolSize:线程池最多可容纳同时执行的线程数,该值大于等于1。keepAliveTime:冗余空闲线程的存活时间。当空间时间达到keepAliveTime的值时,冗余线程将被销毁,直到只剩下corePoolSize个线程。unit:keepAliveTime的单位。workQueue:任务队列,已经提交但还未执行??的任务。threadFactory:表示在线程池中生成工作线程的线程工厂。用户新建线程,一般使用默认。handler:拒绝策略,表示当线程队列满且工作线程大于等于线程池最大显示数(maxnumPoolSize)时,如何拒绝请求执行的runnable策略。而Java的线程池是通过生产者消费者模型实现的。线程池的使用者是生产者,线程池本身是消费者。Java线程池的核心工作流程如下图所示。动手Java线程池我们手动实现的线程池比Java自带的线程池要简单的多。我们去掉了各种复杂的处理方式,只保留了核心原理:线程池的用户向任务队列发送任务,添加任务,线程池自己从任务队列中消费任务并执行。只要理解了这个核心原理,后面的代码就会简单很多。在实现这个简单的线程池时,我们可以拆解整个实现过程。反汇编后的实现过程是:定义核心字段,创建内部类WorkThread,创建ThreadPool类的构造函数,创建执行任务的方法。定义核心字段首先,我们创建一个名为ThreadPool的Java类,并在该类中定义如下核心字段。DEFAULT_WORKQUEUE_SIZE:静态常量,表示默认的阻塞队列大小。workQueue:模拟实际的线程池,使用阻塞队列实现生产者消费者模型。workThreads:模拟实际的线程池,使用List集合保存线程池内部的工作线程。核心代码如下所示。//默认阻塞队列大小privatestaticfinalintDEFAULT_WORKQUEUE_SIZE=5;//模拟实际线程池使用阻塞队列实现生产者消费者模式privateBlockingQueueworkQueue;//模拟实际线程池使用List集合保存线程池的内部工作ThreadprivateListworkThreads=newArrayList();创建一个内部类WordThread在ThreadPool类中创建一个内部类WorkThread来模拟线程池中的工作线程。主要功能是消费workQueue中的任务,执行任务。由于工作线程需要不断从workQueue中获取任务,所以这里使用了一个while(true)循环,不断尝试消费队列中的任务。核心代码如下所示。//内部类WorkThread,模拟线程池中的工作线程//主要作用是消费workQueue中的任务并执行//由于工作线程需要不断从workQueue中获取任务,while(true)循环用于不断尝试消费队列中的Taskstake();workTask.run();}catch(InterruptedExceptione){e.printStackTrace();}}}}创建ThreadPool类的构造函数这里我们为ThreadPool类创建两个构造函数,其中一个是传入线程池的容量和阻塞queue,另外一个构造方法中,只传入了线程池的容量,核心代码如下所示。//在ThreadPool构造方法中,传入线程池大小和阻塞队列publicThreadPool(intpoolSize,BlockingQueueworkQueue){this.workQueue=workQueue;//创建poolSize工作线程,加入workThreads集合IntStream.range(0,poolSize).forEach((i)->{WorkThreadworkThread=newWorkThread();workThread.start();workThreads.add(workThread);});}//在ThreadPool构造方法中引入线程池的大小publicThreadPool(intpoolSize){this(poolSize,newLinkedBlockingQueue<>(DEFAULT_WORKQUEUE_SIZE));}在ThreadPool类中创建执行任务的方法创建执行任务的方法execute()。execute()方法的实现比较简单,就是将该方法接收到的Runnable任务加入到workQueue队列中。核心代码如下所示。//通过线程池执行任务publicvoidexecute(Runnabletask){try{workQueue.put(task);}catch(InterruptedExceptione){e.printStackTrace();}}完整源码这里给出完整源码手动实现的ThreadPool线程池源码,如下图。packageio.binghe.thread.pool;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.BlockingQueue;importjava.util.concurrent.LinkedBlockingQueue;importjava.util.stream.IntStream;/***@authorbinghe*@version1.0.0*@descriptionCustomthreadpool*/publicclassThreadPool{//默认阻塞队列大小privatestaticfinalintDEFAULT_WORKQUEUE_SIZE=5;//模拟实际线程池使用阻塞队列实现生产者-消费者模式privateBlockingQueueworkQueue;//模拟实际的线程池,使用List集合保存线程池内部的工作线程privateListworkThreads=newArrayList();//在ThreadPool构造方法中,传入线程池的大小和阻塞队列publicThreadPool(intpoolSize,BlockingQueueworkQueue){this.workQueue=workQueue;//创建poolSize个工作线程并加入到workThreads集合中IntStream.range(0,poolSize).forEach((i)->{WorkThreadworkThread=newWorkThread();workThread.start();workThreads.add(workThread);});}//在ThreadPool构造方法中,传入线程池的大小publicThreadPool(intpoolSize){this(poolSize,newLinkedBlockingQueue<>(DEFAULT_WORKQUEUE_SIZE));}//通过线程池执行任务publicvoidexecute(Runnabletask){try{workQueue.put(task);}catch(InterruptedExceptione){e.printStackTrace();}}//内部类WorkThread,模拟线程池中的工作线程//主要作用是消费任务在workQueue中,并执行//因为工作线程需要不断从workQueue中获取任务,所以使用while(true)循环不断尝试消费队列中的任务{//当没有任务时,就会blocktry{RunnableworkTask=workQueue.take();workTask.run();}catch(InterruptedExceptione){e.printStackTrace();}}}}}是的,我们只是用几十行Java代码实现了一个简化版Java线程池。是的,这个简化版Java线程池的代码体现了Java线程池的核心原理。接下来,让我们测试一下这个简化版。Java线程池。编写测试程序测试程序也比较简单,就是在main()方法中调用ThreadPool类的构造函数,传入线程池的大小,创建ThreadPool类的实例,然后调用ThreadPool类的execute()方法循环10次,提交给线程池的任务是:打印当前线程的名字--->>HelloThreadPool。整体测试代码如下。packageio.binghe.thread.pool.test;importio.binghe.thread.pool.ThreadPool;importjava.util.stream.IntStream;/***@authorbinghe*@version1.0.0*@descriptionTest自定义线程池*/publicclassThreadPoolTest{publicstaticvoidmain(String[]args){ThreadPoolthreadPool=newThreadPool(10);IntStream.range(0,10).forEach((i)->{threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"--->>HelloThreadPool");});});}}接下来运行ThreadPoolTest类的main()方法,会输出如下信息。Thread-0--->>HelloThreadPoolThread-9--->>HelloThreadPoolThread-5--->>HelloThreadPoolThread-8--->>HelloThreadPoolThread-4--->>HelloThreadPoolThread-1--->>HelloThreadPoolThread-2--->>HelloThreadPoolThread-5--->>HelloThreadPoolThread-9--->>HelloThreadPoolThread-0--->>HelloThreadPool至此,我们自定义的Java线程池就开发完成了。综上所述,线程池的核心原理其实并不复杂。只要我们耐心地分析它,深入它的源码去理解线程池的核心本质,你就会发现线程池的设计是如此的优雅。希望通过这个手写的线程池小例子,能够让大家更加了解线程池的核心原理。本文转载自微信公众号“冰河科技”,可通过以下二维码关注。转载本文请联系冰川科技公众号。