回顾

什么是线程,什么是进程?

  • 进程:是一个应用程序,里面包含很多线程
  • 线程:进程执行的基本单元

java实现线程的几种方式

  • 继承Thread类
  • 实现Runable接口

线程的生命周期

执行线程会出现的问题

  • 一个线程只能执行一个任务
  • 线程执行完后销毁,不能复用
  • 线程过多,会导致jvm宕机(正常一台8核【同时并行的线程最多只有8个,cpu会在2000个线程中来回切】,12g的服务器线程数正常只有2000个线程)

线程池

JUC

  • JUC是一个工具类,用户高并发、处理多线程的一个包。

线程池解决了哪些问题

  • 降低资源消耗
  • 方便线程数的管控
  • 功能强大,提供延时定时线程池

线程池引发了什么问题

  • jvm宕机,提交的任务会消失
  • 使用不合理,导致内存溢出
  • 参数多,引入数据结构和算法,增加了学习难度

线程池的设计思想

  • 线程维护

  • 执行任务

  • 状态监控

线程池的原理

  • 线程池的结构图
  • 最常用的是ThreadPoolExecutor,调度用ScheduleExecutorService;

线程池的工作状态

  • RUNNING:线程池一单被创建,就是RUNNING状态。
  • SHUTDOWN:不接受新的任务,但能处理已添加的任务。调用shutdown接口后,线程池的状态由RUNNING变成SHUTDOWN。
  • STOP:不接受新的任务,不会去处理已经添加的任务,并且会中断住在处理的任务;【调用shutdownnow()接口之后线程池会由running和shutdown变成stop】
  • TIDYING(终止状态):所有任务终止,队列中任务数量会变成0。会执行钩子函数terminated()方法。

线程池的参数定义

线程池的结构说明

  • 任务提交给线程池,如果有空闲线程,则会将任务分配给空闲线程,如果没有空闲线程会先放到队列中去。
  • 如果核心线程都满了,并且队列也满了,才会去只用最大线程数中的线程。
  • 达到maxSize,根据拒绝策略处理。

线程池的工具类

确定线程池的线程数

创建合适的线程数才能提高性能

  • io密集型任务
    io操作时间长,cpu利用率不高,这类任务CPU常处于空闲状态。

此类型任务可以开cpu核心的两倍线程。比如cpu是4核的,可以开8个线程。

  • cpu密集型任务
    执行计算任务,cpu一直运行,cpu的利用率高 。

取相等的线程数。比如cpu是4核的,可以开4个线程。

  • 混合型任务
    既要执行逻辑运算,又要进行大量的io操作。针对不同类型的任务,创建不同的线程池。

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

线程池的源码分析

  • ExecutorService executorService = Executors.newFixedThreadPool(5);
  • ExecutorService executorService1 = Executors.newSingleThreadExecutor();
  • ExecutorService executorService2 = Executors.newCachedThreadPool();
    任务先放在阻塞队列中在进行处理
  • ExecutorService executorService3 = Executors.newScheduledThreadPool(5);

为什么大厂里禁止使用Executors工具类,怕使用不规范造成宕机的风险。一般都是自己new一个线程池:ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 0L, TimeUnit.HOURS, new LinkedBlockingQueue<>(1000));

threadPoolExecutor.execute(); //提交一个普通的任务
threadPoolExecutor.submit(); //提交一个有返回值的任务

  • execute()
//提交任务代码public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();//判断工作数,如果小于coreSize -> addWork,注意第二个参数 core=trueif (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}//如果核心线程都被占满了,判断当前线程池是否是running状态,如果是,则通过offer方法,将当前任务添加至任务队列if (isRunning(c) && workQueue.offer(command)) {//在检查一下状态int recheck = ctl.get();//如果线程池不是运行状态,直接移除任务,并启动拒绝策略if (! isRunning(recheck) && remove(command))reject(command);//如果当前可运行的线程为0的话,表明只去创建线程,并不执行任务(因为,任务已经在上面的offer方法中被添加到了workQueue中了,等待线程池中的线程去消费队列中的任务)else if (workerCountOf(recheck) == 0)//null -----  只创建线程,不去启动//false -----  添加线程时,根据maximumPoolSize来判断addWorker(null, false);}//线程不是正在运行状态或者队列已满,则会进入这里        //由于调用addWorker的第二个参数是false,则表示对比的是最大线程数,那么如果往线程池中创建线程依然失败,即addWorker返回false,那么则进入if语句中,直接调用reject方法调用拒绝策略了else if (!addWorker(command, false))reject(command);}
  • addWorker()
 private boolean addWorker(Runnable firstTask, boolean core) {retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);//如果线程池的状态时运行中(RUNNING),就可以继续往下执行了//如果线程池的状态时关闭(SHUTDOWN)并且firstTask=null并且阻塞队列workQueue中存在没有执行完毕的任务,那么就可以继续往下执行了if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;for (;;) {int wc = workerCountOf(c);//线程大于 2^29次方^-1,或者线程超出了规定的范围,返回falseif (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get();if (runStateOf(c) != rs)continue retry;}}//创建work放入works集合(一个hashSet)boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {//符合条件,创建新的work包装成taskw = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;//加锁,works是一个hashset,保证线程安全性mainLock.lock();try {int rs = runStateOf(ctl.get());if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {//add成功新的work,work立即启动t.start();workerStarted = true;}}} finally {if (! workerStarted)addWorkerFailed(w);}return workerStarted;}
  • 任务获取和执行
//执行runWorker()的时候,一直循环,如果携带task,就执行
while (task != null || (task = getTask()) != null)private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}int wc = workerCountOf(c);// Are workers subject to culling?//判断是不是超时处理,决定了当前线程是不是要释放boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;//线程数量超出max,并且上次循环poll等待超时,说明该线程已经终止,将线程数量原子性 减if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {//计数器原子性递减,递减成功后,放回null,for终止if (compareAndDecrementWorkerCount(c))return null;//递减失败,继续下一轮循环continue;}try {//如果线程可被释放,那就poll,释放时间为keepAliveTime//否则,线程不会被释放,take一直被阻塞在这里,直到新的任务继续工作Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)return r;//到这说明可被释放的线程等待超时,已被销毁,设置标记,下次循环的线程减少timedOut = true;} catch (InterruptedException retry) {timedOut = false;}}}

线程池的经典面试题

  1. 线程池如何保证线程不被销毁

如果队列没有任务,核心线程一直阻塞获取任务的方法,直到返回任务。而任务执行完后,又会进入下一轮work.runWork()中循环。

//核心代码
//work.runWork
while (task != null || (task = getTask()) != null)//work.getTaskboolean timed = allowCoreThreadTimeOut || wc > corePoolSize;//最关键Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS):workQueue.take();
  1. 核心线程和非核心线程有区别吗?

没有,被销毁的线程和创建先后无关。即使是第一个被创建的核心线程,仍有可能被销毁。
**验证:**每个work在runwork()的时候去getTask(),在getTask内部,并没有针对性的区分当前work是否是核心线程。只要判断work是都大于core,就会调poll(),否则take()。

  1. 阅读代码,查看执行结果

结果只会执行 1 和 2,因为队列不会满,只会执行核心线程数,而核心线程在 while(true) 中一直在执行。

  1. 线程池7个参数作用和生效时机
  • int corePoolSize,
  • int maximumPoolSize,
  • long keepAliveTime,
  • TimeUnit unit,
  • BlockingQueue workQueue,
  • ThreadFactory threadFactory
  • RejectedExecutionHandler handler
  1. 为什么线程池不用数组,而用队列?

狂野java前置课程-线程池的基本使用相关推荐

  1. Java常用四大线程池用法以及ThreadPoolExecutor详解

    2019独角兽企业重金招聘Python工程师标准>>> 为什么用线程池? 1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处-理效率 2.线程并发数量过多 ...

  2. Java并发编程——线程池的使用

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

  3. 四十七、面试前,必须搞懂Java中的线程池ThreadPoolExecutor(上篇)

    @Author:Runsen @Date:2020/6/9 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰 ...

  4. java异常_Java线程池「异常处理」正确姿势:有病就得治

    假设我们有一个线程池,由于程序需要,我们向该线程池中提交了好多好多任务,但是 这些任务都没有对异常进行try catch处理,并且运行的时候都抛出了异常 .这会对线程池的运行带来什么影响? 正确答案是 ...

  5. 自定义线程池-java内置线程池构造方法介绍

    Java内置线程池原理剖析 我们要想自定义线程池,必须先了解线程池的工作原理,才能自己定义线程池:这里我们通过观察java中ThreadPoolExecutor的源码来学习线程池的原理; Thread ...

  6. Java多线程之线程池配置合理线程数

    Java多线程之线程池配置合理线程数 目录 代码查看公司服务器或阿里云是几核的 合理线程数配置之CPU密集型 合理线程数配置之IO密集型 1. 代码查看公司服务器或阿里云是几核的 要合理配置线程数首先 ...

  7. Java多线程之线程池的手写改造和拒绝策略

    Java多线程之线程池的手写改造和拒绝策略 目录 自定义线程池的使用 四种拒绝策略代码体现 1. 自定义线程池的使用 自定义线程池(拒绝策略默认AbortPolicy) public class My ...

  8. Java多线程之线程池7大参数、底层工作原理、拒绝策略详解

    Java多线程之线程池7大参数详解 目录 企业面试题 线程池7大参数源码 线程池7大参数详解 底层工作原理详解 线程池的4种拒绝策略理论简介 面试的坑:线程池实际中使用哪一个? 1. 企业面试题 蚂蚁 ...

  9. Java多线程之线程池详解

    Java多线程之线程池详解 目录: 线程池使用及优势 线程池3个常用方式 线程池7大参数深入介绍 线程池底层工作原理 1. 线程池使用及优势 线程池做的工作主要是控制运行的线程的数量,处理过程中将任务 ...

最新文章

  1. 乔布斯在斯坦福大学毕业典礼上的演讲
  2. java HashMap 极限容量 大小限制 占用内存大小
  3. Linux 学习_ssh(secure shell)
  4. php中花括号的使用
  5. React开发(103):详细路径 不然找不到
  6. 前端学习(2602):什么是跨域请求和跨域请求数据数据的表现
  7. JS核心基础数组的操作概述
  8. SVM-支持向量机(code实现)
  9. 小学奥数 地球人口承载力估计
  10. live2d_一款电脑桌面跨平台开源免费live2D桌面宠物精灵
  11. 雷达通信一体化波形设计综述
  12. 如何将数据库删除干净
  13. 工作模板-----MySQL示例
  14. Centos7做回收站功能,防止误删除
  15. SQL学习笔记(02)_别名
  16. vue2+vant适配750设计稿
  17. 青云诀2显示登录服务器超时,青云诀2游戏突然显示数据包损坏怎么办 解决方案分享...
  18. python三维数据转换成二维_用Python生成马赛克画
  19. 爬虫笔记:BeautifulSoup详解
  20. xxl-job源码解析(技术分享)

热门文章

  1. ipad密码忘了怎么解锁?教你40秒轻松移除!
  2. 黄渤沈腾吐槽宁浩拍戏疯狂 徐峥自曝出演外星人
  3. i5 10600kf和R5 3600X哪个好
  4. 阅读通用订阅源写法和源
  5. SSH与SS两者4大区别和基于原理和协议43.228.67.1
  6. Ubuntu18 安装ROS节点解决----速腾聚创雷达点云格式转换为Velodyne雷达点云格式 --SLAM不学无术小问题
  7. 教女朋友学习 vue的生命周期钩子函数
  8. Windows安装达梦数据库及初始化
  9. 谷歌浏览器 添加 HttpWatch 扩展程序
  10. oracle 12c tns,Oracle 11g TNS-12547错误