Java 四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingl

1,098 阅读6分钟

这是我参与更文挑战的第 8 天,活动详情查看

Java通过Executors提供四种线程池,分别为:

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

1. 介绍一下Executors

public class Executorsextends Object
此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。此类支持以下各种方法:

创建并返回设置有常用配置字符串的 ExecutorService 的方法。 创建并返回设置有常用配置字符串的 ScheduledExecutorService 的方法。 创建并返回“包装的”ExecutorService 方法,它通过使特定于实现的方法不可访问来禁用重新配置。 创建并返回 ThreadFactory 的方法,它可将新创建的线程设置为已知的状态。 创建并返回非闭包形式的 Callable 的方法,这样可将其用于需要 Callable 的执行方法中。

2.ThreadFactory简单介绍

ThreadFactory是一个线程工厂。用来创建线程。这里为什么要使用线程工厂呢?其实就是为了统一在创建线程时设置一些参数,如是否守护线程。线程一些特性等,如优先级。通过这个TreadFactory创建出来的线程能保证有相同的特性。它首先是一个接口类,而且方法只有一个。就是创建一个线程。

public interface ThreadFactory {    
    Thread newThread(Runnable r);  
}  

Executors提供了defaultThreadFactory()实现

    public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }

在JDK中,有实现ThreadFactory就只有一个地方。而更多的时候,我们都是继承它然后自己来写这个线程工厂的。

  下面的代码中在类Executors当中。默认的 我们创建线程池时使用的就是这个线程工厂; 给线程池中的线程设置线程group、统一的线程前缀名。以及统一的优先级。

static class DefaultThreadFactory implements ThreadFactory {  
    private static final AtomicInteger poolNumber = new AtomicInteger(1);//原子类,线程池编号  
    private final ThreadGroup group;//线程组  
    private final AtomicInteger threadNumber = new AtomicInteger(1);//线程数目  
    private final String namePrefix;//为每个创建的线程添加的前缀  
  
    DefaultThreadFactory() {  
        SecurityManager s = System.getSecurityManager();  
        group = (s != null) ? s.getThreadGroup() :  
                              Thread.currentThread().getThreadGroup();//取得线程组  
        namePrefix = "pool-" +  
                      poolNumber.getAndIncrement() +  
                     "-thread-";  
    }  
  
    public Thread newThread(Runnable r) {  
        Thread t = new Thread(group, r,  
                              namePrefix + threadNumber.getAndIncrement(),  
                              0);//真正创建线程的地方,设置了线程的线程组及线程名  
        if (t.isDaemon())  
            t.setDaemon(false);  
        if (t.getPriority() != Thread.NORM_PRIORITY)//默认是正常优先级  
            t.setPriority(Thread.NORM_PRIORITY);  
        return t;  
    }  
}

3. newFixedThreadPool

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads(池中的线程数 ) 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

 @Test
    public void test01() throws InterruptedException {
        /**返回用于创建新线程的默认线程工厂。此工厂创建同一 ThreadGroup 中 Executor 使用的所有新线程。
         * 如果有 SecurityManager,则它使用 System.getSecurityManager() 组来调用此 defaultThreadFactory 方法,其他情况则使用线程组。
         * 每个新线程都作为非守护程序而创建,并且具有设置为 Thread.NORM_PRIORITY 中较小者的优先级以及线程组中允许的最大优先级。
         * 新线程具有可通过 pool-N-thread-M 的 Thread.getName() 来访问的名称,其中 N 是此工厂的序列号,M 是此工厂所创建线程的序列号。
         */
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2,threadFactory);
        //ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
        for (int i=0; i<10;i++){
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        Thread.sleep(10000);
    }

4.newCachedThreadPool

如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程

@Test
    public void test02() throws InterruptedException {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 10; i > 0; i--) {
            final int index = i;
            try {
                Thread.sleep(index * 500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(4000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(index+"***"+Thread.currentThread().getName());
                }
            });
        }
      Thread.sleep(40*1000);
    }

5.newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行(可以做定时循环任务)。延迟执行示例代码如下

 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        //延迟执行示例代码如下
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("delay 3 seconds");
            }
        }, 3, TimeUnit.SECONDS);//表示延迟3秒执行。

        //定期执行示例代码如下:
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("delay 1 seconds, and excute every 3 seconds");
            }
        }, 1, 3, TimeUnit.SECONDS);//表示延迟1秒后每3秒执行一次。

6.newSingleThreadExecutor

创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。

ExecutorService singleThreadExecutor=Executors.newSingleThreadExecutor();
        for (int i=0; i<10;i++){
            //结果依次输出,相当于顺序执行各个任务
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

7.为什么要用线程池

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

比较重要的几个类:
ExecutorService真正的线程池接口。
ScheduledExecutorService能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutorExecutorService的默认实现。
ScheduledThreadPoolExecutor继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。