JUC并发编程——线程池(下)

简介: JUC并发编程——线程池(下)

正文


三、四种线程池解析


Executors.newSingleThreadExecutor();


package com.xiaojie.juc.thread.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * @author xiaojie
 * @version 1.0
 * @description: 单线程线程池
 * @date 2021/12/12 22:14
 */
public class SingleThreadExecutorDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i=0;i<5;i++){
            int finalI = i;
            executorService.execute(() -> {
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("这是一个单线程线程池的demo,线程名称:"+Thread.currentThread().getName()+">>>>>>>"+ finalI);
            });
        }
        //关闭线程池
        executorService.shutdown();
    }
}


222.png


由执行结果可知


1、单线程线程池中的任务是按照提交任务的顺序执行的。


2、池中唯一的线程存活时间是无限制的。


3、当池中的线程正在繁忙时,新提交的任务会进入内部阻塞队列,并且阻塞队列是无界的(LinkedBlockingQueue<Runnable>)。


适用场景


单线程线程池适用于任务按照提交次序,一个任务一个任务的逐个执行的场景。


Executors.newFixedThreadPool


package com.xiaojie.juc.thread.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * @author xiaojie
 * @version 1.0
 * @description: 固定长度的线程池
 * @date 2021/12/12 22:29
 */
public class FixedThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("这是一个固定长度的线程池的demo,线程名称:" + Thread.currentThread().getName() + ">>>>>>>" + finalI);
                }
            });
        }
        executorService.shutdown();
    }
}

222.png


有执行结果可知


1、并不是按照任务提交顺序执行的。


2、如果线程数量没有达到固定数量,每次提交都会创建新的线程,直到达到最大数量


3、如果线程洗的大小达到固定数量就会保持不变,如果某个线程因为异常而结束,那么线程池会补充一个新的线程。


4、如果接收到新任务没有空闲线程也会进入阻塞队列(LinkedBlockingQueue<Runnable>)。


适用场景:需要任务长期执行的场景,固定数量的线程数能够避免频繁的创建和销毁线程,例如CPU密集型的任务,在CPU被工作线程长时间占用的情况下,能确保尽可能减少线程分配。


弊端:


内部使用无界队列来存放任务,当大量任务超过线程池能处理的最大容量时队列无限增大,使服务器资源迅速耗尽。


Executors.newCachedThreadPool()


package com.xiaojie.juc.thread.pool;
import java.util.concurrent.*;
/**
 * @author xiaojie
 * @version 1.0
 * @description: 缓存功能的线程池
 * @date 2021/12/12 20:31
 */
public class CachedThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("这是一个缓存功能的线程池的demo,线程名称:" + Thread.currentThread().getName() + ">>>>>>>" + finalI);
                }
            });
        }
        executorService.shutdown();
    }
}


222.png


执行结果可知


1、当任务提交时,如果线程繁忙,会创建新的线程执行任务。


2、对线程池的大小没有限制,底层使用SynchronousQueue<Runnable>队列。


3、如果部分线程空闲,线程数量超过了任务数量,就会回收空闲(60秒不执行任务)线程。


适用场景


需要快速处理突发性强,耗时较短的任务场景,例如Netty的NIO场景,RESTAPI瞬时削峰。可缓存线程池的线程数量不固定,有空闲线程就会自动回收,接收到新任务时判断是否有空闲线程,如果没有就直接创建新的线程。


弊端


线程池没有最大线程数量限制,如果大量的异步任务同时执行,可能会因创建线程过多而导致资源耗尽。


Executors.newScheduledThreadPool


package com.xiaojie.juc.thread.pool;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * @author xiaojie
 * @version 1.0
 * @description: 定时,延迟线程池
 * @date 2021/12/12 23:08
 */
public class ScheduledThreadPoolDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            scheduledExecutorService.schedule(() -> {
                System.out.println("这是一个延迟线程池的demo,延迟5秒后执行,线程名称:" + Thread.currentThread().getName() + ">>>>>>>" + finalI);
            }, 5, TimeUnit.SECONDS);
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("这个方法延迟1秒后执行,然后每隔1秒后重复执行");
                }
            }, 1, 1, TimeUnit.SECONDS);
        }
//        scheduledExecutorService.shutdown();
    }
}


使用 DelayedWorkQueue()队列实现


适用场景


周期性的执行任务的场景,例如一些定时任务的实现,Springboot的任务调度。


四、自定义线程池


Executors创建线程的潜在问题


1、创建newFixedThreadPool的潜在问题在于工作队列,使用LinkedBlockingQueue(无界队列),如果任务的提交速度大于任务的处理速度,就会造成大量的任务在阻塞队列中等待,如果阻塞队列很大,很有可能导致OOM(内存溢出)。


2、创建newSingleThreadExecutor和newFixedThreadPool线程池一样,同样使用LinkedBlockingQueue(无界队列),如果任务的提交速度大于任务的处理速度,就会造成大量的任务在阻塞队列中等待,如果阻塞队列很大,很有可能导致OOM(内存溢出)。


3、newCachedThreadPool线程池的潜在问题在于其核心线程数为0,最大线程数为Integer.MAX_VALUE,使用SynchronousQueue同步队列。如果同时执行大量的任务,就意味会创建大量的线程,可能导致OOM,甚至导致CPU资源耗尽。


4、ScheduledThreadPoolExecutor最大线程数也是Integer.MAX_VALUE,和newCachedThreadPool存在同样的问题。


自己创建线程池


package com.xiaojie.juc.thread.pool;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * @author xiaojie
 * @version 1.0
 * @description: 自定义创建线程池
 * @date 2021/12/12 23:56
 */
public class MyThreadPool {
    //定义工作队列
    BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10);
    public ThreadPoolExecutor threadPoolExecutor(int corePoolSize, int maximumPoolSize, Long keepAliveTime) {
        return new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
                0L, TimeUnit.MILLISECONDS,
                workQueue);
    }
    public static void main(String[] args) {
        MyThreadPool myThreadPool = new MyThreadPool();
        ThreadPoolExecutor executor = myThreadPool.threadPoolExecutor(3, 10, 60L);
        for (int i = 0; i < 20; i++) {
            executor.execute(() -> {
                //最大可以允许20个任务,超过的将进行拒绝策略
                System.out.println("通过ThreadPoolExecutor 定义的线程池" + Thread.currentThread().getName());
            });
        }
    }
}


五、如何确定线程池线程数


1、由于IO密集型任务的CPU使用率低,导致线程空闲时间很多,因此通常需要开CPU 核心数两倍的线程。当IO线程空闲时,可以启用其他线程继续使用CPU,来提高CPU的利用率。


2、如果是CPU密集型,CPU密集型的任务虽然可以并行的执行,但是并行的任务越多,花在线程切换的时间就越多,CPU执行效率就越低,所以一般设置线程数等于CPU的核心数。


3、混合型既要满足IO又要满足CPU密集的计算公式


最佳线程数=((线程等待时间+线程CPU时间)/线程CPU时间)*CPU核数


参考:《JAVA高并发核心编程(卷2):多线程、锁、JMM、JUC、高并发设计》-尼恩编著

相关文章
|
4月前
|
Java 程序员 调度
【JAVA 并发秘籍】进程、线程、协程:揭秘并发编程的终极武器!
【8月更文挑战第25天】本文以问答形式深入探讨了并发编程中的核心概念——进程、线程与协程,并详细介绍了它们在Java中的应用。文章不仅解释了每个概念的基本原理及其差异,还提供了实用的示例代码,帮助读者理解如何在Java环境中实现这些并发机制。无论你是希望提高编程技能的专业开发者,还是准备技术面试的求职者,都能从本文获得有价值的见解。
67 1
|
19天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
2月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
31 3
|
2月前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
34 0
|
3月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
3月前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
164 6
【Java学习】多线程&JUC万字超详解
|
3月前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
61 3
|
3月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
3月前
|
并行计算 API 调度
探索Python中的并发编程:线程与进程的对比分析
【9月更文挑战第21天】本文深入探讨了Python中并发编程的核心概念,通过直观的代码示例和清晰的逻辑推理,引导读者理解线程与进程在解决并发问题时的不同应用场景。我们将从基础理论出发,逐步过渡到实际案例分析,旨在揭示Python并发模型的内在机制,并比较它们在执行效率、资源占用和适用场景方面的差异。文章不仅适合初学者构建并发编程的基础认识,同时也为有经验的开发者提供深度思考的视角。
|
4月前
|
Java 数据库连接 微服务
揭秘微服务架构下的数据魔方:Hibernate如何玩转分布式持久化,实现秒级响应的秘密武器?
【8月更文挑战第31天】微服务架构通过将系统拆分成独立服务,提升了可维护性和扩展性,但也带来了数据一致性和事务管理等挑战。Hibernate 作为强大的 ORM 工具,在微服务中发挥关键作用,通过二级缓存和分布式事务支持,简化了对象关系映射,并提供了有效的持久化策略。其二级缓存机制减少数据库访问,提升性能;支持 JTA 保证跨服务事务一致性;乐观锁机制解决并发数据冲突。合理配置 Hibernate 可助力构建高效稳定的分布式系统。
70 0