当前位置: 首页 > 后端技术 > Java

访谈实战28:创建线程池有几种方式?推荐哪个?

时间:2023-04-01 15:04:45 Java

在Java语言中,并发编程是通过创建线程池来实现的,创建线程池的方法有很多种。每种线程池创建方式对应不同的使用场景。一般来说,线程池的创建可以分为以下两类:通过ThreadPoolExecutor手动创建线程池。通过Executors自动创建线程池。以上两种创建线程池的方法,具体实现方法有7种。这7个实现方法是:Executors.newFixedThreadPool:创建一个固定大小的线程池,可以控制并发线程数。在队列中等待。Executors.newCachedThreadPool:创建一个可缓存的线程池。如果线程数超过处理要求,缓存会在一段时间后回收。如果线程数不够,就会创建新的线程。Executors.newSingleThreadExecutor:创建线程数单一的线程池,可以保证先进先出的执行顺序。Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。Executors.newSingleThreadScheduledExecutor:创建一个可以执行延迟任务的单线程线程池。Executors.newWorkStealingPool:创建线程池,用于抢占式执行(任务执行顺序不确定)【JDK1.8新增】。ThreadPoolExecutor:一种手动创建线程池的方法。创建时最多可以设置7个参数。接下来我们看看这七个线程池的具体使用。1.FixedThreadPool创建一个固定大小的线程池,可以控制并发线程数。使用FixedThreadPool创建两个固定大小的线程池。具体实现代码如下:publicstaticvoidfixedThreadPool(){//创建一个有两个线程的线程池ExecutorServicethreadPool=Executors.newFixedThreadPool(2);//创建任务Runnablerunnable=newRunnable(){@Overridepublicvoidrun(){System.out.println("任务执行完毕,线程:"+Thread.currentThread().getName());}};//线程池执行任务(一次添加4个任务)//执行任务有两种方式:提交和执行threadPool.submit(runnable);//执行方式一:提交threadPool.execute(runnable);//执行方式二:executethreadPool.execute(runnable);threadPool.execute(runnable);}上面程序的执行结果如下图所示:如果觉得上面的方法比较繁琐,也可以使用下面简单的方法来实现线程池的创建和使用:publicstaticvoidfixedThreadPool(){//创建一个线程池ExecutorServicethreadPool=Executors.newFixedThreadPool(2);//执行任务threadPool.execute(()->{System.out.println("任务执行完毕,线程:"+Thread.currentThread().getName());});}2.CachedThreadPool创建可缓存线程水池。如果线程数超过任务要求,多余的线程会被缓存一段时间再被回收。如果线程数不够,会创建一个新的线程CachedThreadPool使用示例如下:publicstaticvoidcachedThreadPool(){//创建一个线程池ExecutorServicethreadPool=Executors.newCachedThreadPool();//执行任务for(inti=0;i<10;i++){threadPool.execute(()->{System.out.println("任务已执行,线程:"+Thread.currentThread().getName());尝试{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){}});}}上面程序的执行结果如下图所示:从上面的结果可以看出,线程池创建了10个线程来执行相应的任务。使用场景CachedThreadPool根据短期任务的多少来决定创建线程的数量,因此适用于处理短时间内有大量突发任务的场景。3.SingleThreadExecutor创建一个单线程的线程池,可以保证先进先出的执行顺序。使用SingleThreadExecutor的例子如下://执行任务for(inti=0;i<10;i++){finalintindex=i;threadPool.execute(()->{System.out.println(index+":taskisexecuted");try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){}});}}上面程序的执行结果如下图所示:单线程的线程池是什么意思?与线程相比,单线程的线程池有以下两个优点:线程可以复用:即使是单线程池也可以复用线程。提供任务管理功能:单个线程池也有一个任务队列,任务队列中可以存放多个任务,这是线程无法实现的,当任务队列满时,可以执行拒绝策略,这是非线程可以实现的在线程中可用。4.ScheduledThreadPool创建一个可以执行延迟任务的线程池。使用示例如下:publicstaticvoidscheduledThreadPool(){//创建线程池ScheduledExecutorServicethreadPool=Executors.newScheduledThreadPool(5);//添加定时执行任务(1s后执行)System.out.println("Addtask,ti??me:"+newDate());threadPool.schedule(()->{System.out.println("任务执行完毕,时间:"+newDate());try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){}},1,TimeUnit.SECONDS);}上面程序的执行结果如下图所示:从上面的结果可以看出任务是在1秒后执行的,而任务是在延迟了1秒。5.SingleThreadScheduledExecutor创建一个单线程线程池,可以执行延时任务。这个线程池可以看作是单线程池版本的ScheduledThreadPool。它的使用示例如下://添加定时执行任务(2s后执行)System.out.println("Addtask,ti??me:"+newDate());threadPool.schedule(()->{System.out.println("Taskexecuted,time:"+newDate());try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){}},2、TimeUnit.SECONDS);}上面程序的执行结果如下图所示:从上面的结果可以看出任务是在2秒后执行的。6、newWorkStealingPool创建一个线程池,用于抢占式执行(任务执行顺序不定)。这个方法是JDK1.8新增的,所以只能在JDK1.8以上的程序中使用。newWorkStealingPool的使用示例如下:publicstaticvoidworkStealingPool(){//创建线程池ExecutorServicethreadPool=Executors.newWorkStealingPool();//执行任务for(inti=0;i<10;i++){finalintindex=i;threadPool.execute(()->{System.out.println(index+"已执行,线程名称:"+Thread.currentThread().getName());});}//Makesurethetaskisexecutedwhile(!threadPool.isTerminated()){}}上面程序的执行结果如下图所示:从上面的结果可以看出任务的执行顺序不确定,因为它是抢先执行的。7.ThreadPoolExecutorThreadPoolExecutor是最原始也是推荐的手动创建线程池的方式。最多提供7个参数供创建时设置。ThreadPoolExecutor的使用示例如下:publicstaticvoidmyThreadPoolExecutor(){//创建线程池ThreadPoolExecutorthreadPool=newThreadPoolExecutor(5,10,100,TimeUnit.SECONDS,newLinkedBlockingQueue<>(10));//执行任务for(inti=0;i<10;i++){finalintindex=i;threadPool.execute(()->{System.out.println(index+"已执行,线程名称:"+Thread.currentThread().getName());try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}});}}上面程序的执行结果如下图所示:ThreadPoolExecutor相对于其他线程池创建的优点是可以通过参数控制最大任务数和拒绝策略,使得线程池的执行更加透明可控,所以阿里巴巴里面规定《Java开发手册》:【强制要求】线程池不允许使用Executors来Creation,而是通过ThreadPoolExecutor这种处理方式让写的同学更清楚运行规则线程池,避免资源耗尽的风险。注意:Executors返回的线程池对象的缺点如下:1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能累积大量请求,导致OOM。2)CachedThreadPool:允许创建的线程数为Integer.MAX_VALUE,可能会创建大量线程,导致OOM。总结一下,创建线程池有七种方式:Executors.newFixedThreadPool:创建一个固定大小的线程池,可以控制并发线程数,多余的线程会在队列中等待。Executors.newCachedThreadPool:创建一个可缓存的线程池。如果线程数超过处理要求,缓存会在一段时间后回收。如果线程数不够,就会创建新的线程。Executors.newSingleThreadExecutor:创建线程数单一的线程池,可以保证先进先出的执行顺序。Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。Executors.newSingleThreadScheduledExecutor:创建一个可以执行延迟任务的单线程线程池。Executors.newWorkStealingPool:创建线程池,用于抢占式执行(任务执行顺序不确定)【JDK1.8新增】。ThreadPoolExecutor:一种手动创建线程池的方式。创建时最多可以设置7个参数。推荐使用最后一个ThreadPoolExecutor方法创建线程池,因为使用它可以明确线程池的运行规则,避免资源耗尽的风险。判断是非在己,名誉在人,得失在数。公众号:Java面试真题分析面试合集:https://gitee.com/mydb/interview