java中的5种常见线程池

时间:2024-03-07 09:48:12

一、线程池简介

      周所周知,Java创建一个新线程的成本是比较高的。因此在面临大量的多线程任务时,采用线程池几乎成了惯用的做法,线程池其实也是设计模式中享元模式思想的一种应用。

     一般线程池刚启动时会新建大量的(跟传入参数有关)空闲线程,程序将一个Runnable或者Callable对象传给线程池时,线程池会调用空闲线程执行他们的run()方法或者call()方法。执行完成后并不回收该线程,而是再次返回线程池中称谓空闲线程,等待下一次任务。

二、Java线程池

     Java 5以前,开发者需要手动实现线程池,从Java 5 开始,Java支持了内建线程池,提供了一个Executors类来新建线程池,它位于java.util.concurrent包下,基本上所有跟并发编程相关的都在该包下面。

     Java 常用的线程池有7种,他们分别是:

          (1)newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。


          (2)newFixedThreadPool:创建一个固定数目的、可重用的线程池。

          (3)newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。

          (4)newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

          (5)newSingleThreadScheduledExcutor:创建一个单例线程池,定期或延时执行任务。

          (6)newWorkStealingPool:创建持有足够线程的线程池来支持给定的并行级别,并通过使用多个队列,减少竞争,它需要穿一个并行级别的参数,如果不传,则被设定为默认的CPU数量。

            (7) ForkJoinPool:支持大任务分解成小任务的线程池,这是Java8新增线程池,通常配合ForkJoinTask接口的子类RecursiveAction或RecursiveTask使用。

三、示例代码(只介绍代表性的几个)

    (1)newCachedThreadPool 会根据任务来临的需要决定是否创建新的线程,也就是如果来了新任务又没有空闲线程,它就会新建一个线程,下面用代码可以理解这个事情。

package com;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class Main {
 
    public static void main(String[] args) throws Exception {
        
        ExecutorService m=Executors.newCachedThreadPool();
        
        for(int i=1;i<=10;i++){
            final int count=i;
            
            m.submit(new Runnable(){
                @Override
                public void run() {
                    System.out.println("线程:"+Thread.currentThread()+"负责了"+count+"次任务");
                }
                
            });
            //下面这行代码注释的话,线程池会新建10个线程,不注释的话,因为会复用老线程,不会产生10个线程
//            Thread.sleep(1);  
        }
        
    }
}


注释掉Thread.sleep(1)的结果如下——产生了10个线程

线程:Thread[pool-1-thread-1,5,main]负责了1次任务
线程:Thread[pool-1-thread-2,5,main]负责了2次任务
线程:Thread[pool-1-thread-3,5,main]负责了3次任务
线程:Thread[pool-1-thread-4,5,main]负责了4次任务
线程:Thread[pool-1-thread-5,5,main]负责了5次任务
线程:Thread[pool-1-thread-6,5,main]负责了6次任务
线程:Thread[pool-1-thread-7,5,main]负责了7次任务
线程:Thread[pool-1-thread-8,5,main]负责了8次任务
线程:Thread[pool-1-thread-9,5,main]负责了9次任务
线程:Thread[pool-1-thread-10,5,main]负责了10次任务

不注释掉Thread.sleep(1)的结果如下——产生了2个线程
线程:Thread[pool-1-thread-1,5,main]负责了1次任务
线程:Thread[pool-1-thread-2,5,main]负责了2次任务
线程:Thread[pool-1-thread-2,5,main]负责了3次任务
线程:Thread[pool-1-thread-2,5,main]负责了4次任务
线程:Thread[pool-1-thread-2,5,main]负责了5次任务
线程:Thread[pool-1-thread-2,5,main]负责了6次任务
线程:Thread[pool-1-thread-2,5,main]负责了7次任务
线程:Thread[pool-1-thread-2,5,main]负责了8次任务
线程:Thread[pool-1-thread-2,5,main]负责了9次任务
线程:Thread[pool-1-thread-2,5,main]负责了10次任务

    

(2)newFixedThreadPool 创建一个固定大小的、可重用是线程池

package com;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class Main {
 
    public static void main(String[] args) throws Exception {
        
        ExecutorService m=Executors.newFixedThreadPool(4);
        
        for(int i=1;i<=10;i++){
            final int count=i;
            
            m.submit(new Runnable(){
                @Override
                public void run() {
                    System.out.println("线程:"+Thread.currentThread()+"负责了"+count+"次任务");
                }
                
            });
            Thread.sleep(1000);  
        }
        
    }
}

结果如下:

线程:Thread[pool-1-thread-1,5,main]负责了1次任务
线程:Thread[pool-1-thread-2,5,main]负责了2次任务
线程:Thread[pool-1-thread-3,5,main]负责了3次任务
线程:Thread[pool-1-thread-4,5,main]负责了4次任务
线程:Thread[pool-1-thread-1,5,main]负责了5次任务
线程:Thread[pool-1-thread-2,5,main]负责了6次任务
线程:Thread[pool-1-thread-3,5,main]负责了7次任务
线程:Thread[pool-1-thread-4,5,main]负责了8次任务
线程:Thread[pool-1-thread-1,5,main]负责了9次任务
线程:Thread[pool-1-thread-2,5,main]负责了10次任务

     

(3)newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。

package com;
 
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
 
public class Main {
 
    public static void main(String[] args) throws Exception {
 
        // 指定大小为4
        ScheduledExecutorService m = Executors.newScheduledThreadPool(4);
 
        m.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Date now = new Date();
                System.out.println("线程" + Thread.currentThread() + "报时:" + now);
            }
 
        }, 1, 1, TimeUnit.SECONDS); // 延迟1s秒执行,每隔1s执行一次
 
    }
}

结果

线程Thread[pool-1-thread-1,5,main]报时:Thu Sep 10 14:55:15 CST 2015
线程Thread[pool-1-thread-1,5,main]报时:Thu Sep 10 14:55:16 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:17 CST 2015
线程Thread[pool-1-thread-1,5,main]报时:Thu Sep 10 14:55:18 CST 2015
线程Thread[pool-1-thread-1,5,main]报时:Thu Sep 10 14:55:19 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:20 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:21 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:22 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:23 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:24 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:25 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:26 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:27 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:28 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:29 CST 2015
线程Thread[pool-1-thread-2,5,main]报时:Thu Sep 10 14:55:30 CST 2015

(4)newWorkStealingPool创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不穿如并行级别参数,将默认为当前系统的CPU个数。下面用代码来体现这种并行的限制,从结果中可以看到,同一时刻只有两个线程执行。

package com;
 
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class Main {
 
    public static void main(String[] args) throws Exception {
 
        // 设置并行级别为2,即默认每时每刻只有2个线程同时执行
        ExecutorService m = Executors.newWorkStealingPool(2);
 
        for (int i = 1; i <= 10; i++) {
            final int count=i;
            m.submit(new Runnable() {
                @Override
                public void run() {
                    Date now=new Date();
                    System.out.println("线程" + Thread.currentThread() + "完成任务:"
                            + count+"   时间为:"+    now.getSeconds());
                    try {
                        Thread.sleep(1000);//此任务耗时1s
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
 
            });
           
        }
        while(true){
            //主线程陷入死循环,来观察结果,否则是看不到结果的
        }
    }
}

结果:

线程Thread[ForkJoinPool-1-worker-1,5,main]完成任务:1   时间为:7
线程Thread[ForkJoinPool-1-worker-0,5,main]完成任务:2   时间为:7
线程Thread[ForkJoinPool-1-worker-1,5,main]完成任务:3   时间为:8
线程Thread[ForkJoinPool-1-worker-0,5,main]完成任务:4   时间为:8
线程Thread[ForkJoinPool-1-worker-1,5,main]完成任务:5   时间为:9
线程Thread[ForkJoinPool-1-worker-0,5,main]完成任务:6   时间为:9
线程Thread[ForkJoinPool-1-worker-1,5,main]完成任务:7   时间为:10
线程Thread[ForkJoinPool-1-worker-0,5,main]完成任务:8   时间为:10
线程Thread[ForkJoinPool-1-worker-1,5,main]完成任务:9   时间为:11
线程Thread[ForkJoinPool-1-worker-0,5,main]完成任务:10   时间为:11


三、总结

     以上几个线程池基本满足了常用的线程池需求,开发者可根据场景灵活选中,其中可以实现大任务分解的ForkJoinPool线程池的介绍因为篇幅较长,专门单独写了一篇博客,地址是: Java 多线程中的任务分解机制-ForkJoinPool详解


--------------------- 
作者:山中小僧 
来源:CSDN 
原文:https://blog.csdn.net/a369414641/article/details/48342253 
版权声明:本文为博主原创文章,转载请附上博文链接!

相关文章