首页 > 编程语言 >并发编程[10]_线程池

并发编程[10]_线程池

时间:2024-08-23 11:09:19浏览次数:15  
标签:10 log 22 05 编程 线程 2021 ThreadPoolExecutor

本文介绍java中的线程池类ThreadPoolExecutor。

我们可以利用ThreadPoolExecutor创建线程池,这个类中有多个构造方法。

  1. ThreadPoolExecutor(int corePoolSize,  int maximumPoolSize,  long keepAliveTime,  TimeUnit unit,  BlockingQueue<Runnable> workQueue)
    
  2. ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
    
  3. ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
    
  4. ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
    

参数介绍:

corePoolSize : 核心池的大小,即核心线程的数目

maximumPoolSize:线程池最大线程数

keepAliveTime:空闲线程存活时间。表示线程没有任务执行时,多久会终止。

unit:对应keepAliveTime的时间单位

workQueue:阻塞队列,存储等待执行的任务

threadFactory:创建线程时所使用的工厂

handler:拒绝处理任务时的策略,有以下四种取值:

​ ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常

​ ThreadPoolExecutor.CallerRunsPolicy:让调用者运行任务

​ ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中最早的任务,本任务取而代之

​ ThreadPoolExecutor.DiscardPolicy:丢弃任务,不抛异常

当线程池的线程数大于corePoolSize时,就会使用之前的空闲线程。空闲线程数 = maximumPoolSize - corePoolSize,空闲线程有存活时间keepAliveTime,核心线程则没有。

线程提交方法:execute()

线程池使用例子:

public class Test1 {
    private static final Logger log = LoggerFactory.getLogger(Test1.class);

    public static void main(String[] args) throws InterruptedException {
        // 自定义线程创建工厂
        MyThreadFactory myThreadFactory = new MyThreadFactory();
        // 新建阻塞队列,容量为1
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(1);
        // 使用DiscardPolicy策略,拒绝任务时直接丢弃
        RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();

        // 创建线程池,核心池2,总3,空闲1,存活时间5s
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 3, 5, TimeUnit.SECONDS, queue, myThreadFactory, handler);
        
        executor.execute(() -> {
            try {
                Thread.sleep(1000);
                log.debug("1");
            } catch (InterruptedException e) {
                log.debug("被打断1");
            }
        });
        executor.execute(() -> {
            try {
                Thread.sleep(1000);
                log.debug("2");
            } catch (InterruptedException e) {
                log.debug("被打断2");
            }
        });
        executor.execute(() -> {
            try {
                Thread.sleep(1000);
                log.debug("3");
            } catch (InterruptedException e) {
                log.debug("被打断3");
            }
        });
        executor.execute(() -> {
            try {
                Thread.sleep(1000);
                log.debug("4");
            } catch (InterruptedException e) {
                log.debug("被打断4");
            }
        });
        executor.execute(() -> {
            try {
                Thread.sleep(1000);
                log.debug("5");
            } catch (InterruptedException e) {
                log.debug("被打断5");
            }
        });
    }

    // 线程创建工厂
    static class MyThreadFactory implements ThreadFactory {
        private AtomicInteger atomic = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "myThread-" + atomic.getAndIncrement());
        }
    }
}

运行结果:

2021-05-04 17:34:08:323 [myThread-1] - 1
2021-05-04 17:34:08:323 [myThread-3] - 4
2021-05-04 17:34:08:323 [myThread-2] - 2
2021-05-04 17:34:09:348 [myThread-1] - 3

创建了三个线程输出了1 ,4,2,在log输出3的时候,放入了阻塞队列,阻塞队列已满,所以输出5的任务根据策略直接丢弃。

程序的运行没有停止,是因为线程池没有关闭。

根据业务需要,如果需要关闭线程池,可以使用以下方法:

  • shutdown() :不会接收新任务,已提交任务会执行完,会打断不在执行中的任务
  • shutdownNow(): 不会接收新任务,会将队列中的任务返回,并用interrupt打断所有任务。

将上面的例子1的main方法末尾,加上代码:

Thread.sleep(500);
executor.shutdown();

运行结果:

2021-05-04 17:34:48:154 [myThread-1] - 1
2021-05-04 17:34:48:154 [myThread-2] - 2
2021-05-04 17:34:48:169 [myThread-3] - 4
2021-05-04 17:34:49:162 [myThread-1] - 3

程序正确退出,等待队列中的任务也正常执行了。

将上面的例子1的main方法末尾,加上代码

Thread.sleep(500);
executor.shutdownNow();

运行结果:

2021-05-04 17:35:08:977 [myThread-1] - 被打断1
2021-05-04 17:35:08:977 [myThread-2] - 被打断2
2021-05-04 17:35:08:977 [myThread-3] - 被打断4

程序被打断,且阻塞队列中的任务也没有继续执行。

Executors

我们也可以利用Executors工具类来创建线程池,但是并不推荐这样使用。

  • newFixedThreadPool(int nThreads) :能够创建固定大小的线程池,每次有任务进来就会创建一个线程,直到达到线程池的最大值。阻塞队列LinkedBlockingQueue是无界的,可以放任意数量的任务。

        public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    
  • newCachedThreadPool :核心线程是0,使用的都是空闲线程,每个线程都有存活时间。可以无限创建线程。阻塞队列使用的是SynchronousQueue,是一个缓冲区为1的阻塞队列。

        public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    
  • newSingleThreadExecutor:线程数量为1的线程池,类似于newFixedThreadPool(1)。不同的是它的返回值又包装了一层FinalizableDelegatedExecutorService,对外只暴露了ExecutorService接口,不能调用ThreadPoolExecutor中特有的方法,而newFixedThreadPool对外暴露的是ThreadPoolExecutor对象,可以强转后调用相应的方法修改核心线程数等属性

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

ScheduledThreadPoolExecutor

当我们需要延时,定时执行线程的时候,就可以使用ScheduledThreadPoolExecutor来创建线程池。

ScheduledThreadPoolExecutor继承ThreadPoolExecutor ,实现了ScheduledExecutorService。

image

常用方法:

  • 在延迟delay时间后执行任务command:
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

例子: 在三秒后执行输出的任务:

 	public static void main(String[] args) throws InterruptedException {
        log.debug("main");
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
        executor.schedule(()->{
            log.debug("running...");
        },3,TimeUnit.SECONDS);
        executor.shutdown();
    }

运行结果:

2021-05-07 22:12:51.001  [main] - main
2021-05-07 22:12:54.047  [pool-1-thread-1] - running...
  • 周期性地执行任务:

initialDelay: 在给定的初始延迟后,开始执行任务

period:每次执行的周期时间

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

例子: 在0秒延迟后,每隔1秒输出

	public static void main(String[] args) throws InterruptedException {
        log.debug("main");
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
        executor.scheduleAtFixedRate(() -> {
            log.debug("running...");
        }, 0, 1, TimeUnit.SECONDS);
    }

运行结果:

2021-05-07 22:19:10.873  [main] - main
2021-05-07 22:19:10.909  [pool-1-thread-1] - running...
2021-05-07 22:19:11.911  [pool-1-thread-1] - running...
2021-05-07 22:19:12.911  [pool-1-thread-1] - running...
......(略)

每隔一秒输出,但是如果执行的任务超过了定时时间1S,会怎样? 把任务执行时间修改为执行三秒,在运行:

修改:

        executor.scheduleAtFixedRate(() -> {
            try {
                Thread.sleep(3000);
                log.debug("running...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 0, 1, TimeUnit.SECONDS);

运行结果:

2021-05-07 22:22:35.324  [main] - main
2021-05-07 22:22:38.362  [pool-1-thread-1] - running...
2021-05-07 22:22:41.364  [pool-1-thread-1] - running...
2021-05-07 22:22:44.364  [pool-1-thread-1] - running...
......(略)

从运行结果可以看出,程序每隔3秒执行。

程序执行时间大于定时时间的话, 上次任务执行完便会立马执行下一次任务。


如果业务需要上一次程序执行完之后,再开始计时的话,就可以使用scheduleWithFixedDelay方法:

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

例子:

    public static void main(String[] args) throws InterruptedException {
        log.debug("main");
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
        executor.scheduleWithFixedDelay(() -> {
            try {
                Thread.sleep(3000);
                log.debug("running...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 0, 1, TimeUnit.SECONDS);
    }

运行结果:

2021-05-07 22:26:15.436  [main] - main
2021-05-07 22:26:18.479  [pool-1-thread-1] - running...
2021-05-07 22:26:22.481  [pool-1-thread-1] - running...
2021-05-07 22:26:26.483  [pool-1-thread-1] - running...
2021-05-07 22:26:30.484  [pool-1-thread-1] - running...
......(略)

从运行结果可以看出,程序每隔4s (执行时间3s + 定时时间1s)执行。

从上次执行结束后,再延迟delay时间后,才会开始执行下一次任务。

标签:10,log,22,05,编程,线程,2021,ThreadPoolExecutor
From: https://www.cnblogs.com/Aeons/p/18375584

相关文章

  • 转载:国产麒麟v10、UOS系统在线比较两个Word文件的内容差异
    调用PageOffice的WordCompare方法,同时在线打开两个Word文档,可以切换显示其中的一个文档,或者显示两个文档的对比结果,即可实现在线的文档内容比较功能。此功能可以应用在以下方面:文档管理中,比较两个版本Word文档的差别。 在处理文档管理的Web项目中,比较两个版本的Word,指的不是Wo......
  • 如何克服编程过程中遇到的挫折?
    编程的顿挫感通常是指在编程过程中遇到的挫折、困惑或停滞不前的感觉。这种感觉可能由多种因素引起,包括但不限于:技术难题:遇到难以解决的技术问题或bug,长时间找不到解决方案。学习曲线:学习新技术或编程语言时,初期可能会感到困难重重。项目复杂性:面对复杂的项目结构或需求,难以理......
  • 并发编程[5]_wait和notify
    1.wait和notifywait()方法是Object类中的方法,他的作用是让当前线程进入等待状态,而使用notify()方法可以唤醒。wait(long):void,参数是毫秒,表示等待毫秒数,直到时间结束或被唤醒;wait(long,int):void,第一个参数是毫秒,第二个参数是纳秒,如果纳秒在0-999999之间,则第一个......
  • 并发编程[2]_线程的常用方法
    介绍一下线程常用的一些方法1.run()和start()start()方法让线程进入就绪状态run()方法是Runnable中的一个抽象方法,线程启动时就会调用run()方法(1)如果直接调用run()方法,是不会启动新线程的publicclassTest1{privatestaticfinalLoggerlog=LoggerF......
  • 并发编程[1]_线程的创建
    介绍线程创建的两种基本的方法:继承Thread类和实现Runnable接口1.继承Thread类自定义类继承Thread类,重写run()方法importorg.slf4j.LoggerFactory;/***@author:yt*@date:2021/4/1222:09*@description:创建Thread类继承Thread*/publicclassMyTh......
  • 并发编程[3]_java线程的六种状态
    java线程状态1.操作系统进程的五种状态网上找了一张图:2.java线程的六种状态Thread类中getState()方法可以获取线程的状态,返回值是Thread类中的enum类型,取值有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED六种状态。java的线程状态将阻塞状态细分为BLOCKED,WAITING......
  • 大模型重塑软件架构·开启智能编程新纪元 |大模型书籍推荐
    在大模型时代洪流中,架构设计师就像时代舵手,不仅精通传统架构设计精髓,更要拥抱数据洪流与AI智能的浪潮。他们需具备前瞻视野,深入理解大模型技术如何重塑业务逻辑与系统架构,灵活运用云计算、微服务、自动化运维等现代技术栈,构建高可用、可扩展、智能化的系统架构。今天,小编......
  • 编程创建一个Cale计算类,在其中定义2个变量表示两个操作数,定义四个方法实现求和、差、
    1publicclassHomework06{2//编写一个main方法3publicstaticvoidmain(String[]args){45Calecale=newCale(2,10);6System.out.println("和="+cale.sum());7System.out.println("差="+cale.minus());......
  • 更懂你的文心快码 Inline Chat 全新上线,带来更加简化交互式的编程体验!
    更懂你的文心快码 InlineChat 全新上线,带来更加简化交互式的编程体验,充分适应程序员的编程和使用习惯。代码行内集成、智能生成、智能问答,无需跳出编辑区,AI就在你手边看,开发过程更流畅。那么Inlinechat如何助力实际开发场景?CoCo为大家展开讲讲。更懂你的文心快码......
  • bat编程
    .bat文件(批处理文件)是Windows系统中用于自动化执行一系列命令的脚本文件。下面是一些.bat文件的基本语法和常用命令:1.注释使用REM命令或@echooff后面的行(在@echooff生效的情况下)来添加注释。注释不会被执行,仅用于说明。REM这是一个注释@echooff::这也是一......