第一节:线程池 痛定思痛。 2023-10-02 04:12 1阅读 0赞 ## 第一节:多线程与分布式 ## -------------------- ### 1-1、周课介绍: ### 1. 线程池的基本使用、特点、注意点 2. ThreadLocal的基本使用、原理、注意事项 3. 分布式基础、核心概念 4. Docker的下载、安装和基本使用命令 5. 独立制作Docker容器 6. Nginx的安装、基本使用和常用命令 7. 使用Nginx搭建文件服务 8. 消息队列RabbitMQ的核心概念queue、message和exchange 9. RabbitMQ的四种交换机模式 10. SpringBoot整合RabbitMQ实例 ### 2-1、初识线程池 ### 初始化线程池——线程池,治理线程的法宝 1. 线程池的自我介绍 2. 新建和停止线程池 3. 常见线程池的特点和用法 4. 任务大多线程,如何拒绝 5. 钩子方法,给线程池加点料 6. 实现原理,源码分析 7. 使用线程池的注意点 * 线程池的重要性: * 什么是池:软件中的“池”,可以理解为计划经济,避免创建线程带来的大开销,可复用线程,可控制资源总量。 如果不使用线程池,每一个任务新开一个线程: * 一个线程 EveryoneTaskOneThread.java package threadpool; public class EveryTaskOneThread { public static void main(String[] args) { Thread thread = new Thread(new Task()); thread.start(); } static class Task implements Runnable{ @Override public void run() { System.out.println("执行力任务"); } } } * for循环创建线程 FoeLoop.java package threadpool; public class ForLoop { public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new Task()); thread.start(); } } static class Task implements Runnable{ @Override public void run() { System.out.println("执行力任务"); } } } * 当数量上升到1000 package threadpool; public class ForLoop { public static void main(String[] args) { for (int i = 0; i < 1000; i++) { Thread thread = new Thread(new Task()); thread.start(); } } static class Task implements Runnable{ @Override public void run() { System.out.println("执行力任务"); } } } 这样开销大,我们希望有固定数量的线程,来执行这1000个线程,这样就避免了反复创建并销毁线程所带来的开销问题。 ##### 问题一:反复创建线程开销大 ##### ##### 问题二:过多的线程会占用太多内存 ##### 解决以上两种问题的思路: 1. 用少量的线程——避免内存的过多使用 2. 让这部分线程都保持工作,且可以反复执行任务——避免生命周期的损耗 #### 线程池的好处: #### * 加快响应速度 * 合理利用CPU和内存 * 统一管理资源 #### 线程池使用的场景: #### 服务器接收大量请求时,使用线程池技术是非常合适的,可以大大减少线程的创建和销毁次数,提高服务器的工作效率 实际上,在开发过程中,如果需要创建5个以上的线程,那么就可以使用线程池来管理。 ### 3-1 线程增减的时机 ### * 线程池构造方法的参数 * 线程池是该手动创建还是自动创建 * 线程池里的线程数量设置多少比较合适 * 停止线程池的正确方法 <table> <thead> <tr> <th>参数名</th> <th>类型</th> <th>含义</th> </tr> </thead> <tbody> <tr> <td>corePoolSize</td> <td>int</td> <td>核心线程数</td> </tr> <tr> <td>maxPoolSize</td> <td>int</td> <td>最大线程数</td> </tr> <tr> <td>keepAliveTime</td> <td>long</td> <td>保持存活时间</td> </tr> <tr> <td>workQueue</td> <td>BlockingQueue</td> <td>任务存储队列</td> </tr> <tr> <td>threadFactory</td> <td>ThreadFactory</td> <td>当线程池需要新的线程的时候,会使用threadFactory来生成新的线程</td> </tr> <tr> <td>Handler</td> <td>RejectedExecutionHandler</td> <td>由于线程池无法接受你所提交的任务的拒绝策略</td> </tr> </tbody> </table> * corePoolSize指的是核心线程数——线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池等待有任务到来时,再创建线程去执行任务 * 最大量maxPoolSize\-在核心线程数的基础上,额外增加的线程数的上线。 ![maxPoolSize][] #### 添加线程的规则 #### 1. 如果线程数小于corePoolSize,创建一个新线程来运行任务。 2. 如果线程数等于(或大于)corePoolSize但少于maximumPoolSize,则将任务放入队列。 3. 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程。 4. 如果队列已满,且线程数大于或等于maxPoolSize,则拒绝。 ![添加线程流程图][watermark_type_ZHJvaWRzYW5zZmFsbGJhY2s_shadow_50_text_Q1NETiBA6LevfumAlA_size_20_color_FFFFFF_t_70_g_se_x_16_pic_center] #### 是否需要增加线程的判断顺序是: #### * corePoolSize * workQueue * maxPoolSize * 比喻:烧烤店的桌子 举个例子: * 线程池:核心池大小为5,最大池大小为10,队列为100. * 因为线程中的请求最多会创建5个,然后任务将会被添加到队列中,直到达到100,当队列已满时,将创建最新的线程maxPoolSize,最多到10个线程,如果再来任务,就拒绝。 #### 增减线程的特点 #### 1. 通过设置corePoolSize 和 maximumPoolSize 相同,就可以创建固定大小的线程池 2. 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。 3. 通过设置maximumPoolSize为很高的值,就可以允许线程池容纳任意数量的并发任务。 4. 只有在队列填满时才创建多于corePoolSize的线程,如果使用的是无界队列,那么线程数就不会超过corePoolSize。 ### 3-2 线程存活时间和工作队列 ### #### keepAliveTime #### 如果线程池当前的线程数多于corePoolSize,那么多余的线程空闲时间超过keepAliveime,他们就会被终止 #### ThreadFactory 用来创建线程 #### * 默认使用Executors.defaultThreadFactory() * 创建出来的线程都在同一个线程组 * 如果自己指定threadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程等等 #### workQueue 工作队列 #### 常见3种队列类型: 1. 直接交接: SynchronousQueue 2. 无界队列: LinkedBlockingQueue 3. 有界对列: ArrayBlochingQueue ### 3-4 守护线程 ### 作用:给用户线程提供服务 在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) ,分类标准是线程是否 会阻止JVM的停止——只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部 继续工作;只有当最后一个非守护线程结束时,所有守护线程才会随着JVM一同结束工作。(非守 护线程等同于用户线程) 我们知道,Java虚拟机通常会继续执行线程,直到发生以下两种中的任一情况时,Java程序才能运 行结束: 1.已调用System.exit()方法 2.所有非守护程序线程的线程都已结束 而一般情况下我们不会调用System.exit()方法,所以大部分的Java程序的结束都是由于所有用户线 程都结束而导致的。 所以可以认为,任何一个守护线程都是整个JVM中所有用户线程(非守护线程)的管家。Daemon 的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它是一 个很称职的守护者。 守护线程的特性 线程类型默认继承自父线程 守护线程创建的线程为默认是守护线程,同样,用户线程创建的线程默认为用户线程。非守护线程 如果想创建一个守护线程,需要调用Thread.setDaemon来设置它(Thread类用布尔值daemon属性 来表示线程是否是守护线程),并且,该方法必须在start之前调用,否则会抛出 IllegalThreadState Exception 异常。 被谁启动? 通常由JVM启动,而不是由用户去启动。当JVM启动时,通常会有一个非守护线程(通常为执行ma in函数的线程)。 不影响JVM退出 当只剩下守护线程时,JVM就会退出,因为如果只剩下守护线程,就没必要继续运行程序了。 守护线程没结束并不会影响JVM的正常停止:假设所有用户线程都结束了,那么就算有5个守护线程 正在运行,JVM也会正常停止: 守护线程和普通线程的区别 User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经 全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了,这是因为没有了“被守护 者”,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。 这就是守护线程的作用:告诉JVM不需要等待它退出,当JVM中所有的线程都是守护线程的时候,JV M就可以正常的退出了。 我们是否需要给线程设置为守护线程? 我们通常不应把自己的线程设置为守护线程,因为设置为守护线程是很危险的。比如线程正在访问 如文件、数据库的时候,所有用户线程都结束了,那么守护线程会在任何时候甚至在一个操作的中 间发生中断,所以守护线程永远不应该去访问固有资源。 代码案例: 定义三个线程类,分别是ThreadA,ThreadB,ThreadC。继承Thread类,重写run()方法,在方法 中循环输出线程名称以及执行次数。其中ThreadA,ThreadB为用户线程,循环输出5次。ThreadC 为守护线程,循环输出20次(次数太少无法观察到效果,可以增加循环次数,例如循环输出50次, 100次等) ### 线程池手动创建还是自动创建 ### * 手动创建更好,因为这样可以更加明确线程池的运行规则,避免资源耗尽的风险 * 自动创建线程池(即直接调用JDK封存好的方法)可能带来哪些风险? package threadpool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 演示 newFixedThreadPoolTest */ public class FixedTreadPoolSize { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(4); for (int i = 0; i < 1000; i++) { executorService.execute(new Task()); } } } class Task implements Runnable{ @Override public void run() { try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()); } } } #### newFixedThreadPool #### 易造成大量内存占用,可能会导致OOM 调整一下,把内存调小一些,“Edit Configuration”->VM Options : -Xmx8m -Xms8m package threadpool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 演示newFixedThreadPoolSize出错的情况 */ public class FixedThredPoolSizeOOM { private static ExecutorService executorService = Executors.newFixedThreadPool(1); public static void main(String[] args) { for (int i = 0; i < Integer.MAX_VALUE; i++) { executorService.execute(); } } } class SubThread implements Runnable{ @Override public void run() { try { Thread.sleep(100000000); } catch (InterruptedException e) { e.printStackTrace(); } } } #### newFixedThreadPool #### 当请求堆积时,可能会占用大量的内存 <table> <thead> <tr> <th>Parameter</th> <th>FixedThreadPool</th> <th>cached</th> <th>Scheduled</th> <th>Single</th> </tr> </thead> <tbody> <tr> <td>corePoolSize</td> <td>constructor_args</td> <td>0</td> <td>constructor-args</td> <td>1</td> </tr> <tr> <td>maxPoolSize</td> <td>same as corePoolSize</td> <td>Integer.MAX_VALUE</td> <td>Integer.MAX_VALUE</td> <td>1</td> </tr> <tr> <td>keepAliveTime</td> <td>0 seconds</td> <td>60 seconds</td> <td>0 seconds</td> <td>0 seconds</td> </tr> </tbody> </table> [maxPoolSize]: https://img-blog.csdnimg.cn/5ceb72521db34d57bf5a7fdd12894f04.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LevfumAlA==,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center [watermark_type_ZHJvaWRzYW5zZmFsbGJhY2s_shadow_50_text_Q1NETiBA6LevfumAlA_size_20_color_FFFFFF_t_70_g_se_x_16_pic_center]: https://img-blog.csdnimg.cn/d471b007a8314818bc2dc4cc2bbca4d8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LevfumAlA==,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center
相关 Java 线程池、Runnable线程池、Callable线程池 线程池: 其实就是一个容纳多个线程的容器,其中的线程可以反复的使用,省去了频繁创建和销毁过程对象的操作,无需反复创建线程面消耗过多资源。 为什么要用线程池: 合理 青旅半醒/ 2023年02月26日 12:30/ 0 赞/ 52 阅读
相关 线程、线程池 创建线程的3种方法: package com.frank.threadPool.createThread; / @author 小石潭记 布满荆棘的人生/ 2022年10月22日 04:27/ 0 赞/ 385 阅读
相关 线程池 1.所谓线程池,就是程序的初始化阶段,就预先创建一批线程,每个线程都做好准备干活; 2.然后有一个任务列表,一开始为空,当有任务来了,就往任务列表里面添加;这个任务列表 痛定思痛。/ 2022年06月13日 13:22/ 0 赞/ 329 阅读
相关 线程池 西施越溪女,明艳光云海 最近用线程池和不用线程池做了个速度的测试,在这里备注下: 结果是速度不相上下; public static void main(Str 妖狐艹你老母/ 2022年05月20日 02:35/ 0 赞/ 283 阅读
相关 线程池 线程池 Java里面线程池的顶级接口是 java.util.concurrent.Executor , 但是严格意义上讲 Executor并不是一个线程池,而只是一个 迈不过友情╰/ 2022年03月06日 14:34/ 0 赞/ 401 阅读
相关 线程池 线程池 > 从字面义上来讲,是指管理一组同构工作线程的资源池。线程池是与工作队列密切相关的,其中在工作队列中(Worker Queue)保存了所有等待执行的任务。工作者( 清疚/ 2021年12月11日 03:35/ 0 赞/ 388 阅读
相关 线程池 可preStart一个或全部core thread 0,小于core则来一个任务建一个线程(firstTask),队列,额外线程,拒绝 一个AtomicInteger的 今天药忘吃喽~/ 2021年11月23日 03:40/ 0 赞/ 412 阅读
相关 线程池 1、先创建线程池 import java.util.concurrent.ArrayBlockingQueue; import java.util.concu 拼搏现实的明天。/ 2021年11月09日 14:28/ 0 赞/ 422 阅读
还没有评论,来说两句吧...