欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

JAVA多线程-线程池

时间:2023-07-05
线程池的主要工作

线程池做的工作主要是控制运行的线程的数量。处理过程中将任务放入队列,然后在线程创建之后启动这些任务。如果线程数量超过了最大数量,超出数量的线程排队等待。等待其它线程执行完毕之后,再从队列中取出任务来执行。 特点 线程复用

减低了资源的消耗。通过复制利用已经创建的线程降低线程创建和销毁造成的资源消耗。 控制最大并发数

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

提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性。线程池可以进行统一的分配,调优和监控。 如何使用

ExecutorService 接口继承了 Executor 接口。我们一般用 Executor 接口的池化对象来进行多态。就像我们创建 ArrayList 来多态 List接口 而不是 Collection 接口。 架构说明

|

继承关系编码实现

下边几种,测试线程池用用就行了。

一般自己用构造函数初始化一个线程池。

固定线程数

可控制线程最大并发数,超出的线程会在队列中等待。

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

Executors.newFixedThreadPool(int) // 执行长期的任务来说的话性能会好很多

public class ThreadPoolDemo01 { public static void main(String[] args) { // ExecutorService 接口继承了 Executor 接口 // 我们一般用 Executor 接口的池化对象来进行多态 // 就像我们创建 ArrayList 来多态 List接口 而不是 Collection 接口 // 一池五个处理线程 ExecutorService threadPool = Executors.newFixedThreadPool(5); // 模拟十个用户来办理业务 // 每个用户就是一个来自外部,来请求线程 try { // 使用 for (int i = 0; i < 10; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "线程t 办理业务。。。"); }); } } catch (Exception e) { e.printStackTrace(); } finally { // 关闭 threadPool.shutdown(); } }}

最多就五个线程在复用满足外部的十个业务请求。 一池一线程

创建一个单线程化的线程池。它只会用唯一的工作线程来执行任务,保证所有任务按照指定的顺序执行。

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

Executors.newSingleThreadExecutor() // 一个任务一个任务的执行场景,唯一的工作线程来执行任务,保证所有任务按照指定的顺序执行

package JUC.threadpool;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadPoolDemo01 { public static void main(String[] args) { // 一个任务一个线程,一池一线程 ExecutorService threadPool = Executors.newSingleThreadExecutor(); try { for (int i = 0; i < 10; i++) { threadPool.submit(() -> { System.out.println(Thread.currentThread().getName() + "线程t 办理业务。。。"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }}

我们可以看出这个线程池中只有一个线程在被使用。 一池多线程

创建一个可缓存线程池。如果线程池长度超过处理的需要,可灵活收回空闲线程,若无可回收,则新建线程。

public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, // 来了任务就创建线程运行,当线程空闲超过60s就给他销毁 new SynchronousQueue());}

Executor.newCachedThreadPool() // 适用:执行很多短期异步的小程序或者负载较轻的服务器

public void autoThreadPool() { // 一池多线程,数量会调整 ExecutorService threadPool = Executors.newCachedThreadPool(); try { for (int i = 0; i < 16; i++) { threadPool.submit(() -> { System.out.println(Thread.currentThread().getName() + "线程t 办理业务。。。"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); }}

线程池中的数量是随请求使用线程的次数来定的。我的电脑最多一个线程池10个线程。 ThreadPoolExecutor类 (重点) 核心线程数

核心线程会一直存在,即使没有任务。 七大参数(重点)

// 线程池中的常驻核心线程数// 当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列中去int corePoolSize// 线程池能够容纳“同时执行的最大线程数”,此值必须大于1int maximumPoolSize// 多余的空余线程的存活时间// 当线程池数量超过 corePoolSize 时,// 同时当空闲时间达到 keepAliveTime 时,// 多余的线程会被销毁,直到只剩下 corePoolSize 个线程为止long keepAliveTime// keepAliveTime的单位TimeUnit unit // 任务队列,被提交但是未被执行的任务,也就是阻塞队列BlockingQueue workQueue // 表示生成线程池中工作线程的线程工厂// 用于创建线程,一般用默认的即可ThreadFactory threadFactory // 拒绝策略。表示当队列满了并且工作线程数大于等于线程池的最大线程数 maxmumPoolSize 时,// 如何来拒绝任务RejectedExecutionHandler handler

构造函数

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { // 非法参数检测 if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); // 非法引用检测 if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); // this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler;}

线程池工作原理(重要)

对应图中 1、2、3、4 四个步骤:

如果核心线程使用数量到了 corePoolSize,那么新的任务先放阻塞队列里,如果阻塞队列满了,那么就增加核心线程数,此时增加的核心线程数不是指常驻的核心线程,是指临时的核心线程,当增加的临时核心线程数等于 maximumPoolSize 的时候,就不再往线程池中添加新的临时线程。如果此时阻塞队列和线程池线程数量都达到阈值,并且还源源不断的有新的请求,此时就触发拒绝机制。最后等到阻塞队列为空,线程池中没有线程有要处理的代码段,此时我们判断临时核心线程的等待时间是否等于 keepAliveTime,如果等于,那就销毁临时核心线程。最后我们处理完一切事物,就==只留下了空空的阻塞队列以及我们线程池的两个常驻核心线程==。

|

图示拒绝策略(重要)

以下四个JDK内置的拒绝策略均实现了 RejectedExecutionHandler 接口。

AbortPolicy(默认)

直接抛出 RejectedExecutionException 异常。

阻止系统正常运行。

private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

CallerRunsPolicy

**“调用者运行”**机制,是一种调节机制。该策略既不会抛弃任务,也不会抛出异常。它会将某些任务回退给调用者,从而降低新任务的流量。

DiscardOldestPolicy

抛弃队列中等待最久的任务。然后把当前任务加入到队列中尝试再次提交当前任务。

DiscardPolicy

直接丢弃该任务。不处理,不抛异常,如果允许任务丢失的话,这是最好的一种方案。 自定义线程池案例 未使用自定义拒绝策略 AbortPolicy版

public class CustomThreadPoolDemo { public static void main(String[] args) { ExecutorService threadPool = new ThreadPoolExecutor(2,5 ,60L, TimeUnit.MILLISEConDS , new linkedBlockingQueue(3) , Executors.defaultThreadFactory() ,new ThreadPoolExecutor.AbortPolicy()); try { // 此时阻塞队列和线程池最大长度为 8,同时并发超过 8 的话就会造成拒绝 for (int i = 0; i < 9; i++) { //for (int i = 0; i < 8; i++) { // 不会被拒绝 // 并发 9 threadPool.submit(() -> { System.out.println(Thread.currentThread().getName() + "线程t 处理业务!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }}

CallerRunsPolicy版

public class CustomThreadPoolDemo { public static void main(String[] args) { ExecutorService threadPool = new ThreadPoolExecutor(2,5 ,60L, TimeUnit.MILLISEConDS , new linkedBlockingQueue(3) , Executors.defaultThreadFactory() ,new ThreadPoolExecutor.CallerRunsPolicy()); try { // 超出线程池允许最大并发度 for (int i = 0; i < 9; i++) { threadPool.submit(() -> { System.out.println(Thread.currentThread().getName() + "线程t 处理业务!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }}

其它俩个慎用。 使用自定义拒绝策略 如何合理的配置线程池线程数 CPU密集型

CPU密集的意思是:

该任务需要大量的运算,如果没有阻塞,CPU一直全速运行。CPU的密集任务只有在真正的多核CPU上才可能得到加速(通过多线程)。所以单核CPU就别想加速了,再怎么加速也只有一个核心,最快也就一个核心的算力。

先查看我们的CPU核心数,再进一步去设置 corePoolSize 的大小。

System.out.println(Runtime.getRuntime().availableProcessors());

一般我们的CPU密集型任务配置尽可能少的线程数量。

公式大概是:CPU核心数+1个线程的线程池。

IO密集型

即该任务需要大量的IO,即大量的阻塞。

单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。

所以在IO密集型任务中使用多线程可以大大的加速程序的运行。

在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间,所以也能有加速效果。

因为IO密集型的任务线程不一定一直在执行任务。

则应该配置尽可能多的线程。

一、CPU核数 * 2二、CPU核数 / (1 - 阻塞系数) (阻塞系数在0.8~0.9) 死锁 是什么?

是指两个或者两个以上的线程或进程在执行的过程中,因抢夺资源而造成的一种互相等待的现象。造成这种现象后如果没有外力干涉那么就无法进行下去,这就是死锁。如果系统资源充足,线程或进程的资源请求都能满足,死锁出现的可能性就会很低,而不是不会出现。

|

图示造成原因

系统资源不足。进程运行推进顺序不合适。资源分配不当。请求并持有资源,互斥,不可中断,循环等待。 编码

package JUC.deadlock;import java.util.concurrent.TimeUnit;class HoldThread implements Runnable{ private String lock1; private String lock2; public HoldThread(String lockA, String lockB) { this.lock1 = lockA; this.lock2 = lockB; } @Override public void run() { // 可重入的前提是同一个锁,所以这里不是可重入锁 synchronized (lock1) { System.out.println(Thread.currentThread().getName() + "线程t 持有" + lock1 + "t正在尝试获得" + lock2); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { } } }}public class DeadLockDemo { public static void main(String[] args) { String lockA = "lockA"; String lockB = "lockB"; new Thread(new HoldThread(lockA,lockB),"A").start(); new Thread(new HoldThread(lockB,lockA),"B").start(); }}

字符串是常量,所以说,两个线程抢的锁是相同的两把锁。如果是别的引用的话,这里的锁就是4把不同的锁,那么例子就出错。而且可能出现,A线程已经全部运行完了获得了两把锁,B线程一把锁都没获得的情况,也可能反过来。 定位分析

linux:

ps -ef|grep xxxx

windows:

windows下的java程序运行,也有类似linux下的查看进程的ps命令,但是目前我们只看java。jps.exe = java ps 可以用 jps -ljps -l 查看正在运行的java程序,找到可疑进程的PID。jstack PID 查看当前进程的异常栈,有没有报出死锁。

Java stack information for the threads listed above:==================================================="B": at JUC.deadlock.HoldThread.run(DeadLockDemo.java:35) - waiting to lock <0x00000007160d5f48> (a java.lang.String) - locked <0x00000007160d5f80> (a java.lang.String) at java.lang.Thread.run(Thread.java:748)"A": at JUC.deadlock.HoldThread.run(DeadLockDemo.java:35) - waiting to lock <0x00000007160d5f80> (a java.lang.String) - locked <0x00000007160d5f48> (a java.lang.String) at java.lang.Thread.run(Thread.java:748)Found 1 deadlock.D:DevelopmentEnvironmentGitGitRepositoryjavase-learning>

码云仓库同步笔记,可自取欢迎各位star指正:https://gitee.com/noblegasesgoo/notes

如果出错希望评论区大佬互相讨论指正,维护社区健康大家一起出一份力,不能有容忍错误知识。—————————————————————— 爱你们的 noblegasesgoo

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。