1. 线程池的创建

线程池的创建⽅法总共有 7 种,但总体来说可分为 2 类:

1. 通过 ThreadPoolExecutor 创建的线程池;
2. 通过 Executors 创建的线程池。

线程池的创建⽅式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过ThreadPoolExecutor 创建的):

1. Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
7. ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。

2 固定数量的线程池(newFixedThreadPool)

public static class FixedThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(2);
        // 方式1
        Future<String> submit = es.submit(() -> Thread.currentThread().getName());
        System.out.println(submit.get());
        // 方式2
        es.execute(() -> System.out.println(Thread.currentThread().getName()));
    }
}
输出:
pool-1-thread-1
pool-1-thread-2

2.1 execute和submit的区别

  • execute和submit都属于线程池的方法,execute只能提交Runnable类型的任务
  • submit既能提交Runnable类型任务也能提交Callable类型任务。
  • execute()没有返回值
  • submit有返回值,所以需要返回值的时候必须使用submit
使用submit可以执行有返回值的任务或者是无返回值的任务;而execute只能执行不带返回值的任务。 

2.2 ⾃定义线程池名称或优先级

/**
     * 定义线程池名称或优先级
     */
public static class FixedThreadPoll {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建线程工厂
        ThreadFactory threadFactory = r -> {
            // !!!!!!!一定要注意:要把任务Runnable设置给新创建的线程
            Thread thread = new Thread(r);
            // 设置线程的命名规则
            thread.setName("我的线程:" + r.hashCode());
            // 设置线程的优先级
            thread.setPriority(Thread.MAX_PRIORITY);
            return thread;
        };
        ExecutorService es = Executors.newFixedThreadPool(2, threadFactory);
        // 参数是接口类,所以lambda的()->{}在括号里面,上面的r->{}是直接一个类,并不是作为入参
        Future<Integer> result = es.submit(() -> {
            int num = new Random().nextInt(10);
            System.out.println("线程优先级" + Thread.currentThread().getPriority() + ", 随机数:" + num);
            return num;
        });
        // 打印线程池返回结果
        System.out.println("返回结果:" + result.get());
    }
}
输出:
线程优先级10, 随机数:0
返回结果:0
提供的功能:

        1. 设置(线程池中)线程的命名规则。

        2. 设置线程的优先级。

        3. 设置线程分组。

        4. 设置线程类型(用户线程、守护线程)。

3 带缓存的线程池(newCachedThreadPool)

public static class CachedThreadPool1 {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService es = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            es.submit(() -> System.out.println("i : " + finalI + "|线程名称:" + Thread.currentThread().getName()));
        }
    }
}
输出:
i : 1|线程名称:pool-1-thread-2
i : 2|线程名称:pool-1-thread-3
i : 0|线程名称:pool-1-thread-1    
优点:线程池会根据任务数量创建线程池,并且在一定时间内可以重复使用这些线程,产生相应的线程池。

缺点:适用于短时间有大量任务的场景,它的缺点是可能会占用很多的资源。

4 单线程线程池(newSingleThreadExecutor)

public static class SingleThreadExecutor1 {
    public static void main(String[] args) {
        ExecutorService es = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            es.submit(() -> System.out.println("线程名:" + Thread.currentThread().getName()));
        }
    }
}
输出:
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1    
单线程的线程池又什么意义?
1. 复用线程。
2. 单线程的线程池提供了任务队列和拒绝策略(当任务队列满了之后(Integer.MAX_VALUE),新来的任务就会拒绝策略)

5 执行定时任务的线程(newScheduledThreadPool)

5.1 延迟执行(一次)

public static class ScheduledThreadPool1 {
    public static void main(String[] args) {
        ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
        System.out.println("添加任务的时间:" + LocalDateTime.now());
        ses.schedule(() -> System.out.println("执行子任务:" + LocalDateTime.now()), 3, TimeUnit.SECONDS);
    }
}
输出:
添加任务的时间:2022-06-13T18:57:19.742726900
执行子任务:2022-06-13T18:57:22.758147900    

5.2 固定频率执行

public static void main(String[] args) {
    ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
    System.out.println("添加任务的时间:" + LocalDateTime.now());
    ses.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + "执行任务:" + LocalDateTime.now()), 2, 4, TimeUnit.SECONDS);
    // 2是第一次多久之后执行,4是定时任务每隔多久执行一次
}
输出:
添加任务的时间:2022-06-13T19:08:21.725930300
pool-1-thread-1执行任务:2022-06-13T19:08:23.741930800
pool-1-thread-1执行任务:2022-06-13T19:08:27.736881200
pool-1-thread-2执行任务:2022-06-13T19:08:31.742574400
pool-1-thread-2执行任务:2022-06-13T19:08:35.743332900    
如果在定义定时任务中设置了内部睡眠时间,比如
    ses.scheduleAtFixedRate((Runnable) () -> {
        System.out.println("执行任务: " + LocalDateTime.now());
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    },2,4, TimeUnit.SECONDS);   
5 > 4 ,哪个值大以哪个值作为定时任务的执行周期

5.3 scheduleAtFixedRate VS scheduleWithFixedDelay

public static class ScheduledThreadPool3 {
    public static void main(String[] args) {
        // 创建线程池
        ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
        System.out.println("添加任务时间:" + LocalDateTime.now());
        // 2s之后开始执行定时任务,定时任务每隔4s执行一次
        ses.scheduleWithFixedDelay(() -> {
            System.out.println(Thread.currentThread().getName() + "执行任务:" + LocalDateTime.now());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 2, 4, TimeUnit.SECONDS);
    }
}
输出:
添加任务时间:2022-06-13T19:20:34.311246
pool-1-thread-1执行任务:2022-06-13T19:20:36.317013100
pool-1-thread-1执行任务:2022-06-13T19:20:41.340114100
pool-1-thread-2执行任务:2022-06-13T19:20:46.362309400
pool-1-thread-2执行任务:2022-06-13T19:20:51.369756400    
scheduleAtFixedRate 是以上⼀次任务的开始时间,作为下次定时任务的参考时间的
scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的。

6 定时任务单线程(newSingleThreadScheduledExecutor)

public static class SingleThreadScheduledExecutor1 {
    public static void main(String[] args) {
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        System.out.println("添加任务时间:" + LocalDateTime.now());
        ses.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + "执行时间:" + LocalDateTime.now()), 2, 4, TimeUnit.SECONDS);
    }
}
输出:
添加任务时间:2022-06-13T19:27:06.143427900
pool-1-thread-1执行时间:2022-06-13T19:27:08.146768100
pool-1-thread-1执行时间:2022-06-13T19:27:12.156625
pool-1-thread-1执行时间:2022-06-13T19:27:16.158957500
pool-1-thread-1执行时间:2022-06-13T19:27:20.156330800    

7 根据当前CPU⽣成线程池(newWorkStealingPool)

public static class WorkStealingPool1 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newWorkStealingPool();
        for (int i = 0; i < 5; i++) {
            service.submit(() -> {
                System.out.println("线程名:" + Thread.currentThread().getName());
            });
            while (!service.isTerminated()) {
            }
        }
    }
}
输出:
线程名:ForkJoinPool-1-worker-1    

8 ThreadPoolExecutor

ThreadPoolExecutor继承自AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize :线程池中核心线程数的最大值
  • maximumPoolSize :线程池中能拥有最多线程数
  • workQueue:用于缓存任务的阻塞队列

当调用线程池execute() 方法添加一个任务时,线程池会做如下判断:

1、如果有空闲线程,则直接执行该任务;
2、如果没有空闲线程,且当前运行的线程数少于corePoolSize,则创建新的线程执行该任务;
3、如果没有空闲线程,且当前的线程数等于corePoolSize,同时阻塞队列未满,则将任务入队列,而不添加新的线程;
4、如果没有空闲线程,且阻塞队列已满,同时池中的线程数小于maximumPoolSize ,则创建新的线程执行任务;
5、如果没有空闲线程,且阻塞队列已满,同时池中的线程数等于maximumPoolSize ,则根据构造函数中的 handler 指定的策略来拒绝新的任务。

1)有界队列:

SynchronousQueue :一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于 阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
ArrayBlockingQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
2)无界队列:

LinkedBlockingQueue:基于链表结构的无界阻塞队列,它可以指定容量也可以不指定容量(实际上任何无限容量的队列/栈都是有容量的,这个容量就是Integer.MAX_VALUE)
PriorityBlockingQueue:是一个按照优先级进行内部元素排序的无界阻塞队列。队列中的元素必须实现 Comparable 接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue 不会保证优先级一样的元素的排序。

链接: 线程池的使用(7种创建方法) ThreadPoolExecutor详解

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐