Executors的四种线程池

一、Java 线程池

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

(1). newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class ThreadClass {
    public static void main(String[] args){
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(() -> System.out.println(index));
        }
        cachedThreadPool.shutdown();
    }
}

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

(2). newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:

public static void fixedThreadPoolTest() {
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i++) {
        final int index = i;
        fixedThreadPool.execute(()->{
            try{
                Thread.sleep(1000);
                System.out.println(index);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        });
    }
    fixedThreadPool.shutdown();
}

因为定长线程数3,sleep了1000ms所以每创建三个线程会停顿一下

(3) newScheduledThreadPool

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

这里想着重说下这个线程池,因为为了搞懂和看到预期效果,这个线程池花费了我较多时间。

首先scheduleWithFixedDelay方法,它的参数分别是创建的线程,初始延时时间,线程每次延时的时间,以及时间单位。

public static void  scheduledThreadPool(){
    ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
 
    ((ScheduledExecutorService) scheduledThreadPool).scheduleWithFixedDelay(()-> { //初始延迟5s,之后每个线程延时2s
        try{
            Thread.sleep(3000);
            System.out.println(df.format(new Date()) +" : "+ Thread.currentThread().getName());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    } ,0,1, TimeUnit.SECONDS);
 
    for(int i=0;i<10;++i){ //延迟50s后再关闭线程池,避免主线程在创建完线程后直接关闭线程池。
        try {
            Thread.sleep(5000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    scheduledThreadPool.shutdown();
 
}

打印结果
2018-12-11 15:12:39 : pool-1-thread-1
2018-12-11 15:12:43 : pool-1-thread-1
2018-12-11 15:12:47 : pool-1-thread-2
2018-12-11 15:12:51 : pool-1-thread-1
2018-12-11 15:12:55 : pool-1-thread-3
2018-12-11 15:12:59 : pool-1-thread-2
2018-12-11 15:13:03 : pool-1-thread-2
2018-12-11 15:13:07 : pool-1-thread-1
2018-12-11 15:13:11 : pool-1-thread-5
2018-12-11 15:13:15 : pool-1-thread-3
2018-12-11 15:13:19 : pool-1-thread-3
2018-12-11 15:13:23 : pool-1-thread-4
2018-12-11 15:13:27 : pool-1-thread-4

从结果看出,它是每四秒执行一次。阅读代码,每个线程内部睡3s,延时时间是1s,它的效果就是等待上个线程执行完且延时时间过了再执行自己;

可用场景比如说,地铁每次到达一个站点花费时间都不同,到达站点后开门,然后必须等待一个固定时间后才去关门准备去下个站点。当然这种情况要把线程核心数设置为1.

与之对应的还有一个scheduleAtFixedRate()方法,和上个方法参数相同。不同的是执行会根据线程执行时间和自己设置的延时时间较大值进行。就是说下一个线程必须等待上一个线程执行时间和手动设置延时时间的最大值时间后才能执行自己。

public static void  scheduledThreadPool(){
    ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
 
    ((ScheduledExecutorService) scheduledThreadPool).scheduleAtFixedRate(()-> { //初始延迟5s,之后每个线程延时2s
        try{
            Thread.sleep(500);
            System.out.println(df.format(new Date()) +" : "+ Thread.currentThread().getName());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    } ,0,8, TimeUnit.SECONDS);
 
    for(int i=0;i<10;++i){ //延迟50s后再关闭线程池
        try {
            Thread.sleep(5000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    scheduledThreadPool.shutdown();
 
}

打印结果
2018-12-11 15:32:28 : pool-1-thread-1
2018-12-11 15:32:36 : pool-1-thread-1
2018-12-11 15:32:44 : pool-1-thread-2
2018-12-11 15:32:52 : pool-1-thread-1
2018-12-11 15:33:00 : pool-1-thread-3
2018-12-11 15:33:08 : pool-1-thread-2
2018-12-11 15:33:16 : pool-1-thread-4

和预期效果相同,另外补充的是这些这个线程池每次只有一个线程执行当前任务,其他线程需要等待时间后才能执行。因为前一个线程没有执行完,下一个线程就调不起来。

采用等待Max(线程任务时间,每次等待时间),可以保证和scheduleWithFixedDelay一样下个线程执行的时候前一个必定执行完成。但如果时间设置的不合理话,可能会造成时间浪费的情况。

该线程池可用于定点执行任务的情况,比如有些任务我就想每隔一段时间执行一次。比如系统发火车票,希望每天的8:00,12:00,16:00…每隔4小时发售一批火车票这种情况。

(4)、newSingleThreadExecutor

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

public static void singleThreadExecutor(){
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        singleThreadExecutor.execute(()->{
            System.out.println(index);
        });
    }
}

结果中index按序输出,并不会出现因为线程串行造成无序情况

二、new Thread的弊端

普通小白的话就是

new Thread(new Runnable() {
	@Override public void run() { 
	// TODO Auto-generated method stub 
	}
 }).start();

那你就out太多了。

  1. new Thread的弊端如下:
    每次new Thread新建对象性能差。
    线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
    缺乏更多功能,如定时执行、定期执行、线程中断。

  2. 相比new Thread,Java提供的四种线程池的好处在于:
    重用存在的线程,减少对象创建、消亡的开销,性能佳。
    可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
    提供定时执行、定期执行、单线程、并发数控制等功能。

三、为什么要用线程池:

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

最后,有什么错误欢迎指正。

有什么问题,欢迎提问。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章