JAVA多线程-线程池

线程池的主要工作

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

特点

线程复用

  • 减低了资源的消耗
  • 通过复制利用已经创建的线程降低线程创建和销毁造成的资源消耗。

控制最大并发数

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

管理线程

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

如何使用

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

架构说明

|[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GPqLQypy-1644327925637)(C:\Users\noblegasesgoo\AppData\Roaming\Typora\typora-user-images\image-20211117110111464.png)]

继承关系

编码实现

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

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

固定线程数
  • 可控制线程最大并发数,超出的线程会在队列中等待
/*
	靠阻塞队列 LinkedBlockingQueue 来初始化
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
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();
        }
    }
}

/*
    pool-1-thread-1线程	 办理业务。。。
    pool-1-thread-4线程	 办理业务。。。
    pool-1-thread-1线程	 办理业务。。。
    pool-1-thread-3线程	 办理业务。。。
    pool-1-thread-2线程	 办理业务。。。
    pool-1-thread-3线程	 办理业务。。。
    pool-1-thread-1线程	 办理业务。。。
    pool-1-thread-4线程	 办理业务。。。
    pool-1-thread-5线程	 办理业务。。。
    pool-1-thread-2线程	 办理业务。。。 
*/
  • 最多就五个线程在复用满足外部的十个业务请求
一池一线程
  • 创建一个单线程化的线程池
  • 它只会用唯一的工作线程来执行任务,保证所有任务按照指定的顺序执行。
/*
	靠阻塞队列 LinkedBlockingQueue 来初始化
*/
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
Executors.newSingleThreadExecutor() // 一个任务一个任务的执行场景,唯一的工作线程来执行任务,保证所有任务按照指定的顺序执行
package JUC.threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author zhaolimin
 * @date 2021/11/17
 * @apiNote 线程池测试
 */
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();
        }
    }
}

/*
    pool-1-thread-1线程	 正在办理业务。。。
    pool-1-thread-1线程	 正在办理业务。。。
    pool-1-thread-1线程	 正在办理业务。。。
    pool-1-thread-1线程	 正在办理业务。。。
    pool-1-thread-1线程	 正在办理业务。。。
    pool-1-thread-1线程	 正在办理业务。。。
    pool-1-thread-1线程	 正在办理业务。。。
    pool-1-thread-1线程	 正在办理业务。。。
    pool-1-thread-1线程	 正在办理业务。。。
    pool-1-thread-1线程	 正在办理业务。。。
*/
  • 我们可以看出这个线程池中只有一个线程在被使用。
一池多线程
  • 创建一个可缓存线程池。
  • 如果线程池长度超过处理的需要,可灵活收回空闲线程若无可回收,则新建线程
/*
	靠阻塞队列 SynchronousQueue 来初始化
*/
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  // 来了任务就创建线程运行,当线程空闲超过60s就给他销毁
                                  new SynchronousQueue<Runnable>());
}
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();
    }
}
/*
    pool-1-thread-1线程	 办理业务。。。
    pool-1-thread-5线程	 办理业务。。。
    pool-1-thread-3线程	 办理业务。。。
    pool-1-thread-2线程	 办理业务。。。
    pool-1-thread-4线程	 办理业务。。。
    pool-1-thread-8线程	 办理业务。。。
    pool-1-thread-7线程	 办理业务。。。
    pool-1-thread-6线程	 办理业务。。。
    pool-1-thread-10线程	 办理业务。。。
    pool-1-thread-9线程	 办理业务。。。
    pool-1-thread-2线程	 办理业务。。。
    pool-1-thread-10线程	 办理业务。。。
    pool-1-thread-9线程	 办理业务。。。
    pool-1-thread-6线程	 办理业务。。。
    pool-1-thread-8线程	 办理业务。。。
    pool-1-thread-7线程	 办理业务。。。
*/
  • 线程池中的数量是随请求使用线程的次数来定的。
  • 我的电脑最多一个线程池10个线程。

ThreadPoolExecutor类 (重点)

核心线程数

  • 核心线程会一直存在,即使没有任务。

七大参数(重点)

// 线程池中的常驻核心线程数
// 当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列中去
int corePoolSize

// 线程池能够容纳“同时执行的最大线程数”,此值必须大于1
int maximumPoolSize

// 多余的空余线程的存活时间
// 当线程池数量超过 corePoolSize 时,
// 同时当空闲时间达到 keepAliveTime 时,
// 多余的线程会被销毁,直到只剩下 corePoolSize 个线程为止
long keepAliveTime
// keepAliveTime的单位
TimeUnit unit
    
// 任务队列,被提交但是未被执行的任务,也就是阻塞队列
BlockingQueue<Runnable> workQueue
    
// 表示生成线程池中工作线程的线程工厂
// 用于创建线程,一般用默认的即可
ThreadFactory threadFactory
    
// 拒绝策略。表示当队列满了并且工作线程数大于等于线程池的最大线程数 maxmumPoolSize 时,
// 如何来拒绝任务
RejectedExecutionHandler handler

构造函数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> 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,如果等于,那就销毁临时核心线程。
    • 最后我们处理完一切事物,就==只留下了空空的阻塞队列以及我们线程池的两个常驻核心线程==。

| **[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jl4Ca1mg-1644327787292)(C:\Users\noblegasesgoo\AppData\Roaming\Typora\typora-user-images\image-20211117145623311.png)]**

图示

拒绝策略(重要)

  • 以下四个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<Runnable>(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();
        }
    }
}
/*
    pool-1-thread-1线程	 处理业务!
    pool-1-thread-5线程	 处理业务!
    pool-1-thread-4线程	 处理业务!
    pool-1-thread-3线程	 处理业务!
    pool-1-thread-2线程	 处理业务!
    pool-1-thread-5线程	 处理业务!
    pool-1-thread-4线程	 处理业务!
    pool-1-thread-1线程	 处理业务!
    java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@3b9a45b3 rejected from java.util.concurrent.ThreadPoolExecutor@7699a589[Running, pool size = 5, active threads = 5, queued tasks = 3, completed tasks = 0]
        at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
        at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
        at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
        at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
        at JUC.threadpool.CustomThreadPoolDemo.main(CustomThreadPoolDemo.java:23)
*/
CallerRunsPolicy版
public class CustomThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(2,5
                ,60L, TimeUnit.MILLISECONDS
                , new LinkedBlockingQueue<Runnable>(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();
        }
    }
}
/*
    pool-1-thread-2线程	 处理业务!
    pool-1-thread-4线程	 处理业务!
    pool-1-thread-2线程	 处理业务!
    main线程	 处理业务! 			// 没报异常也没报错,把业务回退给他的母线程运行,此时这个线程的母线程为main线程
    pool-1-thread-1线程	 处理业务!
    pool-1-thread-3线程	 处理业务!
    pool-1-thread-2线程	 处理业务!
    pool-1-thread-4线程	 处理业务!
    pool-1-thread-5线程	 处理业务!
*/
  • 其它俩个慎用。

使用自定义拒绝策略

如何合理的配置线程池线程数

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)

死锁

是什么?

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

| 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eyGE4Xxa-1644327787299)(C:\Users\noblegasesgoo\AppData\Roaming\Typora\typora-user-images\image-20211118133238601.png)

图示

造成原因

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

编码

package JUC.deadlock;

import java.util.concurrent.TimeUnit;

/**
 * @author zhaolimin
 * @date 2021/11/18
 * @apiNote 死锁测试
 */

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();
    }
}
/*
    A线程	 持有lockA	正在尝试获得lockB
    B线程	 持有lockB	正在尝试获得lockA
    ...
*/
  • 字符串是常量,所以说,两个线程抢的锁是相同的两把锁。
  • 如果是别的引用的话,这里的锁就是4把不同的锁,那么例子就出错。
  • 而且可能出现,A线程已经全部运行完了获得了两把锁,B线程一把锁都没获得的情况,也可能反过来。

定位分析

  • linux

    • ps -ef|grep xxxx
  • windows

    • windows下的java程序运行,也有类似linux下的查看进程的ps命令,但是目前我们只看java
    • jps.exe = java ps 可以用 jps -l
    • jps -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:\DevelopmentEnvironment\Git\GitRepository\javase-learning>
    

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

如果出错希望评论区大佬互相讨论指正,维护社区健康大家一起出一份力,不能有容忍错误知识。
										—————————————————————— 爱你们的 noblegasesgoo
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值