IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 线程池-ThreadPoolExecutor -> 正文阅读

[Java知识库]线程池-ThreadPoolExecutor

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在Java中可以通过线程池来达到这样的效果。

为什么要线程池

多线程技术主要解决处理器单元内多个线程执行的问题,可以减少处理器单元的闲置时间,增加处理器单元吞吐能力,多线程的情况下确实可以最大限度发挥多核处理器的计算能能力,但是如果随意使用线程池,对系统性能反而不利

  • 创建和销毁线程是需要时间:假如一个服务器完成一项任务所需要的时间为:T1表示线程创建时间,T2表示线程执行任务时间,T3表示销毁时间,如果T1+T3远大于T2会得不偿失
  • 线程也需要占用内存空闲,大量的线程会抢占宝贵的内存资源,可能会导致OOM的异常
  • 大量的线程回收也会给GC带来很大的压力,延长GC停顿的时间
  • 大量的线程也会抢占CPU资源,CPU不停的在各个线程上下文切换,反而没有时间去处理线程运行的时候该处理的任务

什么是线程池

线程池就是实现创建若干的可执行的线程放入一个池中,需要的时候从池中获取线程,不用自行创建,使用完成不需要销毁线程而放入线程池中, 从而减少创建和销毁对象的开销

因此通过池资源来避免频繁的创建和销毁线程,让创建的线程进行复用,就有了线程池的概念,线程池里会维护一部分活跃的线程,如果有需要,就去线程池中取线程使用,用完归还到线程池中,免去创建和销毁线程的开销,且线程池也会对线程的数量有一定的限制,线程池的本质是对线程资源的复用。

线程池的优势:

1、降低资源的消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗

2、提高响应速度,当任务到达时,任务可以不需要等待线程创建就可以立即执行

3、提高了线程的可管理性,不仅可以降低系统资源消耗,提高了系统稳定性

4、线程池可以实现统一的分配,调优和监控

线程池的架构

Executor接口是最基础的接口

ExecutorService接口继承了Executor,在其上添加了一些扩展方法,可以说是真正的线程池接口

AbstractExecutorService抽象类实现了ExecutorService中的大部分的接口

ThreadPoolExecutor继承了AbstractExecutorService,是线程池的具体实现

ScheduledExecutorService接口继承自ExecutorService接口,提供了"周期性执行"的功能

ScheduledThreadPoolExecutor类即继承自ThreadPoolExecutor由实现ScheduledExecutorService接口,是”带有周期性执行“功能的线程池

Executors是线程池的静态工厂,提供了快速创建线程池的静态方法

Executor接口:

提交任务接口,只提供了一个execute方法,执行Runable类型任务

public interface Executor {
    void execute(Runnable command);
}

ExecutorService接口

public interface ExecutorService extends Executor {
    //关闭线程池
    void shutdown();
    //立即关闭
    List<Runnable> shutdownNow();
    //是否关闭
    boolean isShutdown();
    //是否终止
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    //提交任务 ,提交任务类型多样,还具有异步功能
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

ExecutorService接口是真正线程池的接口,在Executor基础上做了一些扩展,主要是提交任务,终止任务

其中Future模式是多线程设计常用的一种设计模式。Future模式可以理解成:我有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时间之后,我就便可以从Future那儿取出结果。
Future提供了三种功能:

  • 判断任务是否完成
  • 能够中断任务
  • 能够获取任务执行的结果

向线程池中提交任务的submit方法不是阻塞方法,而Future.get方法是一个阻塞方法,当submit提交多个任务时,只有所有任务都完成后,才能使用get按照任务的提交顺序得到返回结果,所以一般需要使用future.isDone先判断任务是否全部执行完成,完成后再使用future.get得到结果。?

Future接口定义了主要的5个接口方法,有RunnableFuture和SchedualFuture继承这个接口,以及CompleteFuture和ForkJoinTask继承这个接口。

?ScheduledExecutorService接口

ScheduledExecutorService接口提供了具有周期性执行任务的方法,其继承了ExecutorService接口。

public interface ScheduledExecutorService extends ExecutorService {
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

}

Executors创建常见的线程池

Executor工厂类提供线程初始化接口,主要有如下几种

newFixedThreadPool-固定数量的线程池

public class testFixedThreadPool {
    private static AtomicInteger num = new AtomicInteger();
    public static void main(String[] args) {
        //固定数量的线程池
        ExecutorService newThread = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 20; i++) {
            newThread.submit(new Runnable() {
                @Override
                public void run() {
                    Random random = new Random();
                    System.out.println("线程:"+Thread.currentThread().getName()+",执行任务序号:"+num.getAndIncrement());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

?固定线程数量的线程池实现:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

?包括核心线程、最大线程、等待时间、时间单位以及阻塞队列中的LinkedBlockingQueue。

创建一个指定工作线程数的线程池,其中corepoolsize和maxnumPoolSize相等,阻塞队列是基于LinkedBlockingQueue实现的

FixedThreadPool:传入的核心线程数是固定的,所有称为有界线程池,最大线程数和核心线程数相等。

假设核心线程是3,一次性提交20个任务,先启动了3个线程执行3个任务,剩下17个任务进入阻塞队列,因为核心线程和最大线程数相等,所以keepAlivetime参数没有意义,等待任一线程执行结束就会继续从等待队列中获取一个任务进行执行,其优点在于固定线程数量的线程池可以提供程序效率和节省创建线程所消耗的时间开销。

newCachedThreadPool-可缓存工作线程线程池

public class testCachedThreadPool {
    private static AtomicInteger num = new AtomicInteger();
    public static void main(String[] args) {
       //可缓存工作线程的线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            cachedThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    Random random = new Random();
                    System.out.println("线程:"+Thread.currentThread().getName()+",执行任务序号:"+num.getAndIncrement());
                    try {
                        Thread.sleep(random.nextInt(100));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

?具体实现:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

创建一个可缓存的工作线程池,核心线程是0,线程池中线程数可达Integer.MAX_VALUE,即2^32,线程默认存活时间60秒,内部使用的队列SynchronousQueue同步阻塞队列,在没有任务执行时,当线程空闲时间超过60秒,则工作线程将会终止,当提交新任务是,如果没有空闲线程,则创建新线程执行任务可缓存线程至,如果有新的任务且没有可用的空闲线程,则新建线程,如果长时间线程空闲(60s)则对线程进行回收,

此线程池不会线程池大小做线程,线程池大小完全依赖于操作系统能够创建的最大线程大小。

可缓存线程池应用场景适合耗时短,不需要考虑同步的场合

?newSingleThreadExecutor:单个线程的线程池

public class testSingleThreadExecutor {
    private static AtomicInteger num = new AtomicInteger();
    public static void main(String[] args) {
        //固定数量的线程池
        ExecutorService newThread = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 20; i++) {
            newThread.submit(new Runnable() {
                @Override
                public void run() {
                    Random random = new Random();
                    System.out.println("线程:"+Thread.currentThread().getName()+",执行任务序号:"+num.getAndIncrement());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

?底层实现

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

单线程线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的一个线程可以保证提交任务的顺序执行,内部使用LinkedBlockingQueue作为阻塞队列

newFixedThreadPool(1):固定数量的线程的线程池在给定参数为1的情况下就可以看做是newSingleThreadExecutor的同等实现

newScheduledThreadPool:周期性执行任务线程池

public class DIYRunnable implements Runnable{
    private Integer num;

    public DIYRunnable(Integer num) {
        this.num = num;
    }

    @Override
    public void run() {
        System.out.println("线程名:"+Thread.currentThread().getName()+":执行任务编号:"+num+":当前时间:"+System.currentTimeMillis());
    }
}
public class testSchedule {
    public static void main(String[] args) {
        //周期性执行任务的线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        for(int i = 0; i < 3; i++) {
            scheduledExecutorService.scheduleWithFixedDelay(
                    new DIYRunnable(i),2,2,TimeUnit.SECONDS);
        }
    }
}

?底层实现:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

阿里手册说到线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

Executors 返回的线程池对象的弊端如下:

  • FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
  • CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

ScheduledThreadPoolExecutor:周期性线程池


/**
* 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期
* 也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推
* 如果执行任务发生异常,随后的任务将被禁止,否则任务只会在被取消或者Executor被终止后停止
* 如果任何执行的任务超过了周期,随后的执行会延时,不会并发执行
*/
public ScheduledFuture<?> scheduleAtFixedRate(
    Runnable command,
    //初始延时
    long initialDelay,
    //间隔时间
    long period,
    //时间单位
    TimeUnit unit);

/**
* 创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟
* 如果执行任务发生异常,随后的任务将被禁止,否则任务只会在被取消或者Executor被终止后停止
*/
public ScheduledFuture<?> scheduleWithFixedDelay(
    Runnable command,
    long initialDelay,
    long delay,
    TimeUnit unit);

scheduleAtFixedRate:该方法在initialDelay时长后第一次执行任务,以后每隔period时长,再次执行任务。注意,period是从任务开始执行算起的。开始执行任务后,定时器每隔period时长检查该任务是否完成,如果完成则再次启动任务,否则等该任务结束后才再次启动任务

scheduleWithFixDelay:该方法在initialDelay时长后第一次执行任务,以后每当任务执行完成后,等待delay时长,再次执行任务

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-04 15:22:29  更:2022-03-04 15:23:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:51:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码