Java多线程专题之线程池的基本使用

261 阅读6分钟

前言

目前正在出一个Java多线程专题长期系列教程,从入门到进阶含源码解读, 篇幅会较多, 喜欢的话,给个关注❤️ ~

本节主要带大家认识一下线程池以及它的基本使用,一起来看下吧~

为什么要使用线程池

使用线程池主要有以下三个原因:

  1. 创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程
  2. 控制并发的数量,并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。
  3. 可以对线程做统一管理

使用线程池的基本方式

下面给大家介绍常用的几种方式,废话不多说直接看代码

newCachedThreadPool

创建一个线程池,根据需要创建新线程,但在可用时将重用以前构造的线程。这些池通常会提高执行许多短期异步任务的程序的性能。如果可用,对execute的调用将重用以前构造的线程。如果没有可用的现有线程,将创建一个新线程并将其添加到池中。六十秒内未使用的线程将被终止并从缓存中删除。因此,保持空闲足够长时间的池不会消耗任何资源。

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();

        IntStream.range(0, 50).forEach(i -> {
            executor.execute(() -> {
                System.out.println(i);
            });
        });
        
    }
}

从打印结果来看,它是无序的,现在想要有序怎么弄❓还记得之前讲的如何拿到线程执行的异步结果吗,没错,我们可以通过get()方法对线程进行阻塞,但这时候我们需要使用submit()了而不是execute

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();

        IntStream.range(0, 50).forEach(i -> {
//            executor.execute(() -> {
//                System.out.println(i);
//            });

            Future<Integer> future = executor.submit(() -> i);
            try {
                System.out.println(future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

    }
}

现在看,结果都是有序的。其实重点是想告诉大家,submitexecute的区别,submit是提交任务,可以拿到返回值,通过Future对象

newFixedThreadPool

创建一个线程池,该线程池重用在共享无界队列上运行的固定数量的线程。在任何时候,最多nThreads线程将是活动的处理任务。如果在所有线程都处于活动状态时提交了其他任务,它们将在队列中等待,直到有线程可用。如果任何线程在关闭之前的执行过程中由于失败而终止,如果需要执行后续任务,新的线程将取代它。池中的线程将一直存在,直到显式shutdown 。

public class FixedThreadPool {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(10);

        IntStream.range(0, 50).forEach(i -> {
            executor.execute(() -> {
                System.out.println(i);
            });
        });

    }
}

newScheduledThreadPool

创建一个线程池,可以安排命令在给定延迟后运行,或定期执行。corePoolSize – 保留在池中的线​​程数,即使处于空闲状态

顾名思义,提供了定时器的功能,这里不同于前面的两种,它们的执行方式不大一样,我们先看第一种

scheduleAtFixedRate

创建并执行一个周期性动作,该动作在给定的初始延迟后首先启用,随后在给定的周期内启用;也就是说,执行将在initialDelay之后开始,然后是initialDelay+period ,然后是initialDelay + 2 * period ,依此类推。如果任务的任何执行遇到异常,则后续执行将被抑制。否则,任务只会通过取消或终止执行者来终止。如果此任务的任何执行时间超过其周期,则后续执行可能会延迟开始,但不会同时执行。

  • command - 要执行的任务
  • initialDelay – 延迟首次执行的时间
  • period – 连续执行之间的时间段
  • unit – initialDelay 和 period 参数的时间单位
public class ScheduledThreadPool {
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);

        executor.scheduleAtFixedRate(() -> System.out.println("---->" + new Date().getTime()), 1 , 5, TimeUnit.SECONDS);
    }
}

scheduleWithFixedDelay

创建并执行一个周期性操作,该操作首先在给定的初始延迟之后启用,随后在一个执行的终止和下一个执行的开始之间具有给定的延迟。如果任务的任何执行遇到异常,则后续执行将被抑制。否则,任务只会通过取消或终止执行者来终止。

public class ScheduledThreadPool {
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);

        // executor.scheduleAtFixedRate(() -> System.out.println("---->" + new Date().getTime()), 1 , 5, TimeUnit.SECONDS);

        executor.scheduleWithFixedDelay(() -> System.out.println("---->" + new Date().getTime()), 1 , 5, TimeUnit.SECONDS);

    }
}

这就需要大家自己去体验了,可以通过打印时间来看看执行效果~

newSingleThreadExecutor

创建一个使用单个工作线程在无界队列上运行的 Executor。 (但请注意,如果该单线程在关闭前的执行过程中因失败而终止,则如果需要执行后续任务,则新线程将取代它。)任务保证按顺序执行,并且不会有多个任务处于活动状态在任何给定时间。与其他等效的newFixedThreadPool(1)不同,返回的执行程序保证不可重新配置以使用额外的线程。

public class SingleThreadPool {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        IntStream.range(0, 50).forEach(i -> {
            executor.execute(() -> {
                System.out.println(i);
            });
        });

    }
}

结束语

本节主要给大家介绍了线程池的四种基本使用方式,下一节,难度加深, 从源码角度来带大家了解一下线程池的工作原理 ~

往期内容推荐

项目源码(源码已更新 欢迎star⭐️)

推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)