【Java 线程池】【一】线程池介绍和基本使用

发布时间 2023-04-10 07:46:06作者: 酷酷-

1  前言

这节开始我们主要是攻克一下Java中的线程池,来深入的分析一下线程池是怎么设计的,线程池的原理是什么等,只有更好的理解原理,才能很好的使用并且规避掉一些问题,那么本节我们先简单介绍下线程池是什么以及平时大家的使用方法。

2  线程池的使用

大都是为了提升效率,并发的做某些事情;或者是将一个任务拆分成多个小任务,每个小任务都交给线程池去完成,之后将每个任务的结果合并。
或者是一些异步的场景,将任务直接交给线程池去做...等等,使用的场景非常多。先举个例子,我们是怎么使用线程池的:

public class ThreadPoolTest {

    public static ExecutorService createThreadPool() {
        // 创建一个线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                // 核心线程数设置为2
                2,
                // 最大线程数设置为5
                5,
                // 当线程池中的线程数量大于2,这多出的线程在没有任务执行的最大空闲时间,60
                60,
                // 空闲时间的单位,这里设置秒
                TimeUnit.SECONDS,
                // 线程池的阻塞队列使用LinkedBlockingQueue无界阻塞队列
                new LinkedBlockingQueue(),
                // 创建线程的线程工厂,这里test-pool是设置这个线程池的名字
                new DefaultThreadFactory("test-pool"),
                // 当任务太多,线程池线程不足、阻塞队列满时候采取什么拒绝策略
                new ThreadPoolExecutor.CallerRunsPolicy()

        );
        // 返回创建的线程池
        return executor;
    }
    static class DefaultThreadFactory implements ThreadFactory {
        private String name;
        private final AtomicInteger nextId = new AtomicInteger();
        DefaultThreadFactory(String name) {
            this.name = name;
        }
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(null, r, name + "-" + nextId.incrementAndGet());
        }
    }

    public static void main(String[] args) {
        // 创建一个线程池
        ExecutorService executor = createThreadPool();
        for (int i = 0; i < 1000; i++) {
           final int num = i;
           // 封装任务,每个任务就是打印自己是当前第几个号
           Runnable task = () -> {
               System.out.println(num);
           };
           // 往线程池提交任务
           executor.execute(task);
       }
    }
}

上面就是一个线程池使用的小例子:
(1)createThreadPool方法创建出来一个线程池。
(2)然后有1000个任务要跑,每个任务要做的事就是打印自己当前是第几号
(3)封装1000个任务,调用execute方法提交1000个任务

上面的例子是ThreadPoolExecutor线程池,这种线程池是提交任务之后,如果有线程资源空闲会立即执行你提交的任务。
但是其实Java提供的还有另外一种线程池,支持任务的延迟调度、或者任务的周期性的调度的线程池,叫做ScheduledExecutorService,它的使用例子如下:

public class ScheduledThreadPoolTest {
    // 创建一个调度线程池
    public static ScheduledExecutorService createThreadPool() {
        ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(
                // 线程池的核心线程数,配置为3
                3,
                // 创建线程的工厂,test-schedule-pool为线程池的名称
                new DefaultThreadFactory("test-schedule-pool"),
                // 任务拒绝策略,线程资源不足的时候,策略是“使用调用线程池的线程来执行”
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        return executor;
    }

    static class DefaultThreadFactory implements ThreadFactory {
        private String name;
        private final AtomicInteger nextId = new AtomicInteger();
        DefaultThreadFactory(String name) {
            this.name = name;
        }
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(null, r, name + "-" + nextId.incrementAndGet());
        }
    }


    public static void main(String[] args) {
        // 获取调度线程池
        ScheduledExecutorService executor = createThreadPool();
        // 打印当前时间
        System.out.println("当前时间:" + new Date());
        // 立即执行的任务
        Runnable nowTask = () -> {
            System.out.println("当前时间:" + new Date() + "------执行【立即】任务");
        };
        // 延迟执行的任务
        Runnable delayTask = () -> {
            System.out.println("当前时间:" + new Date() + "------执行【延迟】任务");
        };
        // 周期性执行的任务
        Runnable periodTask = () -> {
            System.out.println("当前时间:" + new Date() + "------执行【周期性】任务");
        };

        // 提交立即任务,有线程空闲立即执行
        executor.execute(nowTask);
        // 提交一次性延迟任务,延迟2秒执行
        executor.schedule(delayTask, 2, TimeUnit.SECONDS);
        // 提交周期性的延迟任务,10秒后每3秒执行一次
        executor.scheduleWithFixedDelay(periodTask, 10, 3, TimeUnit.SECONDS);
    }
}

如上图所示,任务的提交时间都是在07:33:39这个时间
(1)其中【立即】任务调用execute方法的任务立即执行了,事实上ScheduleExecutorService继承了ExecutorService接口,具有和他一样的功能,也就是说具有提交任务,线程空闲时立即执行的功能
(2)同时看【延迟】任务,在2秒之后执行了一次,说明具有延迟任务的功能
(3)【周期性】任务,每隔3秒执行一次,说明具有定时任务的功能,是一个定时调度器
上面就是Java提供的两种类型的线程池了,一种是ExecutorService类型的线程池,代表性的子类是ThreadPoolExecutor;
另外一种是ScheduledExecutorService调度类型的线程池,代表性的子类是ScheduledThreadPoolExecutor。
同时JDK为了我们平时线程池的使用方便,提供了一个工具类Executors,里面封装了这两个类型的线程池的创建方法,如下:

public class ExecutorsTest {
    public static void main(String[] args) {
        // 创建一个缓存型的线程池,这种线程池每来一个任务就会创建一个线程来执行
        Executors.newCachedThreadPool();
        // 创建一个固定线程数量的线程池
        Executors.newFixedThreadPool(3);
        // 创建一个单线程的线程池
        Executors.newSingleThreadExecutor();
        // 创建一个单线程的调度线程池
        Executors.newSingleThreadScheduledExecutor();
        // 创建一个固定线程数量的调度线程池
        Executors.newScheduledThreadPool(3);
    }
}

如上代码所示:Executors这个工具类里面封装了蛮多创建线程的方法,来简化我们平时对线程池的创建。
但是平时我们一般是不用Executors这个工具类来创建线程池的,因为Executors里面创建的线程池,参数设置得很不合理,直接使用可能会有很大问题。

3  小结

本节我们简单先回顾下我们平时线程池的使用,下节我们会开始看线程池的实现细节,有理解不对的地方欢迎指正哈。