Java线程池基础

有什么用?

在学一个东西之间,首先就是要知道这个东西怎么用?
线程池就是装有多个线程的容器,为了避免重复创建线程销毁线程所带来的效率问题,线程池就出现了,当需要完成工作时,从线程池中取出空闲的线程去完成工作,完成后,再把线程退还到线程池中。比起我们一个一个new Thread,这种方式带来的好处是非常大的。

基本结构,继承关系

线程池有很多的类,很多的接口,了解他们之间的关系更有助于我们了解线程池
关于线程池的类有:AbstractExecutorService,ThreadPoolExecutor,ScheduledThreadPoolExecutor
关于线程池的接口有:Executor,ExecutorService,ScheduledExecutorService
关于这些类和接口的关系,用一张图来表现
在这里插入图片描述

怎么用?

或许我们先不必要关心原理是什么,内部是怎么样实现的,我们先了解怎么用可以更容易些。
Executors是一个静态工厂,提供了很多创建线程池的方法,通过这些方法可以为我们直接返回一个线程池。
我们来看看这些方法都怎么使用

//创建一个固定线程数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads)
//创建一个线程数量为1的线程池,相当于上一个方法传参为1
public static ExecutorService newSingleThreadExecutor() 
//创建一个可根据实际情况调整线程数量的线程池
public static ExecutorService newCachedThreadPool()
//创建一个可以延迟执行,可以周期性执行的线程池,线程数量为1
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
//功能和上一个方法相同,但是可以可以指定线程的数量
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

下面一个一个使用来演示这些方法是如何创建线程池的。

固定大小数量的线程池

先看代码

public class TestExecutor {
    public static void main(String[] args) {
        ExecutorService executorService =
                Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                System.out.println(System.currentTimeMillis()+"  ThreadID:"+Thread.currentThread().getId());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}
  1. 首先用Executors的newFixedThreadPool静态工厂方法创建一个线程池,用ExecutorService接口接收创建的线程池实例,传参为5,表示线程池中线程的数量为5
  2. 循环10次,循环的内容是线程池实例调用了一个submit方法,submit是给线程池提交任务的方法,回想以前new Thread的时候,是不是要传入一个Runnable的实例,这块也是一样,submit括号中传入的是一个Runnable对象,这块我写成了lambda的形式,花括号里面的内容其实就是run方法里面的内容。
    来看一下结果:
    在这里插入图片描述
    提交的10次任务都完成了,而且在运行的时候是以5个为单位出现的,因为run方法执行需要时间,而线程池此刻规定了只有5个线程,那么线程池中只有5个线程来执行这10个任务,分两波进行,也就是以5个为单位,另外通过线程id也可以看出,只有5个线程。
    这种就是固定线程池中线程的大小创建线程池。
    public static ExecutorService newSingleThreadExecutor() 创建出来的线程池相当于只有一个线程,这块简单提一下,可以自己实现。
可根据实际情况调整线程数量的线程池
public class TestExecutor2 {
    public static void main(String[] args) {
        ExecutorService executorService =
                Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                System.out.println(System.currentTimeMillis()+"  ThreadID:"+Thread.currentThread().getId());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

同样的原理,这次我们使用的是Executors.newCachedThreadPool()这个方法,看一下结果

在这里插入图片描述
明显看到是10个不同的线程在分别执行10个任务,所以以这种方式创建的线程池数量是随着提交的任务变化的。

指定延迟和周期调度的线程池

通过Executors.newScheduledThreadPool(int nThread)这种方法创建的线程池可以指定提交任务执行的延迟时间,周期性循环执行。具体怎么使用,该线程池提供了
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit);
这两种方法的参数有什么意义?
command是要提交的任务
initalDelay是任务执行前的延迟
period是循环执行时前后两个任务的间隔时间
unit是period和initalDelay的时间单位
这两个方法有什么区别?
scheduleAtFixedRate的period是从上一个任务调度开始就计算时间,而scheduleWithFixedDelay是从上一次任务调度结束开始计算时间
看一个demo

public class TestExecutor3 {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService =
                Executors.newScheduledThreadPool(10);
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println(System.currentTimeMillis()/1000);
        },0,2, TimeUnit.SECONDS);
    }
}

这个demo就是周期性的执行提交的任务,每隔2秒执行一次,但是是从任务开始就计时,那会不会有疑问,如果任务执行的时间是10s,时间period设置为2s,那么会不会出现重叠执行?答案是不会,如果任务执行时间大于周期调度的间隔时间,那么下一次任务会在本次任务结束后就立即执行,这个可以自己测试一下。

猜你喜欢

转载自blog.csdn.net/weixin_42220532/article/details/89814643