java虚拟机多线程进阶篇总结

1.线程池基本参数

以Executors.newFixedThreadPool()这种创建方式为例:

大家想象,假如你创建一个线程池,你想这个池子有些什么参数呢?首先这个池子必须要有一个最大值;然后还希望这个池子的线程数量有一个警戒线,到了这个警戒线的位置说明线程池暂时已经满了,如果这个时候还有人过来拿线程,我们就要把这些人抓起来扔到一个地方去让他们排队,告诉他们:请稍等,等我们的线程有空闲的时候再来处理你的事;再然后假如人排队的地方都满了,玛德,好多人,于是线程池就想办法东拼西凑又多搞出来了几个线程去处理了;最后,假如那搞出来的这几个线程还是不够用,并且排队的地方总是满的,于是线程池生气了,就这么多人可以了,如果还有人过来的赶紧让它滚蛋;

这里我们需要知道几个东西:

1这里的警戒线叫做核心线程池大小(corePoolSize);

2.最大值还是叫做线程池线程最大数量(maximumPoolSize)

3.排队的地方叫做队列(BlockingQueue<Runnable> ),这个队列用于保存我们的线程要做的任务,这个队列有好几种类型,我们后面会分析的;

4.还有一个参数是keepAliveTime:线程存活时间,意思就是当池中总共的线程大于核心线程池数目,那就关闭池子中的空闲线程,要保证线程总数维持在核心线程池数目或者之下;

现在我们来理一下逻辑:

池中当前线程数量 <= 核心线程池大小:线程池直接创建线程处理

池中当前线程数量 > 核心线程池数量:将多余的任务放进队列

队列满了,还有任务过来,线程池继续创建线程,直到到达线程池最大数量

还有任务过来,这里会有一个饱和策略,默认是直接丢弃继续过来的任务

2.线程池种类

我们上一节使用的线程池如下所示:

ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(new RunnableImpl("玩游戏"));

我们是通过Executors这个类的静态方法创建的一个线程池,于是进入这个类我们看看这个类还有没有创建其他种类线程池的方法,居然还真有。。。

我们先简单说说这四种分别是干嘛用的;

newFixedThreadPool(int):这个线程池就是上面说的那种方式,也是我们重点要看源码的线程池;

newSingThreadExecutor():这个不能说是线程池了,因为里面这里面只有一个线程,而且自带一个队列,只要有任务来了就会把任务保存到队列中,然后这个线程就慢慢的一个一个执行。

newCachedThreadPool():无限线程的线程池

newScheduledThreadPool(int):一个定时的线程池,可以让线程池中的线程延迟指定时间再执行任务;

3.Executors继承结构

我们可以看到实际上实例化的是一个ThreadPoolExecutor对象,这个对象作用是用线程去处理传进去的任务:

我们看一下这个继承结构,

Executor接口:只是定义了execute();这个方法,等待子类去实现;

ExecutorService接口:继承Execute接口,并又声明了shutdown()方法和submit()方法,等待子类去实现

AbstractExecutorService抽象类:初步实现了submit()方法,但是内部调用的execute()方法去执行任务

ThreadPoolExecutor类:这个类是实现了很多的方法,将shutdown()和execute()方法都给实现了;

4.看看execute()方法源码

下面我们主要就是看看execute()方法的内部是怎么实现的,知道了这个的实现原理也就差不多了 

 public void execute(Runnable command) {
  if (command == null)
   throw new NullPointerException();
   int c = ctl.get();    //workCountOf(c)表示当前线程池中线程的数量;这里进行一个判断,当线程池中线程数目小于核心池子数目时,  就调用addWorker()方法将我们的任务添加进去,等下可以看到addWorker()方法内部其实就是创建线程并处理请求,  就类似new Thread(xxx).start()这种方式     if (workerCountOf(c) < corePoolSize) {
   if (addWorker(command, true))
    return;
   c = ctl.get();
  }

//如果当前线程数目大于核心线程并且任务放入一个队列成功,内部还会再次进行线程池状态判断,这里的&&用得比较精髓(短路作用),好好体会一下,  假如不是运行状态那就会执行remove方法 删除队列中的任务,如果是运行状态直接进入else if,这里的目的是线程池中已经关闭了,我们添加一个null任务  表示线程池不再处理任务      if (isRunning(c) && workQueue.offer(command)) {
   int recheck = ctl.get();
   if (! isRunning(recheck) && remove(command))
    reject(command);
   else if (workerCountOf(recheck) == 0)
    addWorker(null, false);
  }

//能执行到这里,说明上面两个if中的条件都不满足,条件应该是:当前线程大于核心线程,并且向队列中添加任务失败,换句说说就是对列已经满了,装不下这么多任务  于是我们reject()方法内部就是对这些多余的任务进行处理的一些策略,默认就是直接丢弃
  else if (!addWorker(command, false))
   reject(command);
 }

对于面这三种情况的判断还是很清楚的,我们忽略很多细节,因为我们的目的是要对整个逻辑有个大概的了解,而不是去完全消化这些源码,这很不现实,要想理解透彻只能慢慢的去研究...

我们来看看最重要的addWorker()这个方法,这个方法就是线程池将我们传进来的new Runnable(xxx)进行处理,其实内部就是用new Thread(xxxx).start()处理,只是出于线程池中会进行很多的条件判断以及将Runnable()做进一步的封装,我们了解就好,代码如下:

private boolean addWorker(Runnable firstTask, boolean core) {
  //这里删除很多的条件判断的代码    ..........         boolean workerStarted = false;
  boolean workerAdded = false;
  Worker w = null;
  try {
   final ReentrantLock mainLock = this.mainLock;
   //注意下面这两行,其实就是将我们传进来的Runnable()进行封装成Worker,在Worker构造器里面会new Thread()并且保存起来       这样做的一个好处就是直接将一个线程和一个Runnable进行绑定,我们随时可以从Worker中获取线程然后调用start()方法就ok了       w = new Worker(firstTask);
   final Thread t = w.thread;
   if (t != null) {
    mainLock.lock();
    try {
     //此处删除一些
            .........
     if (rs < SHUTDOWN ||
      (rs == SHUTDOWN && firstTask == null)) {
      if (t.isAlive()) // precheck that t is startable
       throw new IllegalThreadStateException();
              //由于会有很多个Worker,于是我们会创建HashSet<Worker> workers = new HashSet<Worker>(),用于保存所有的worker,后续直接遍历处理很方便              而且我们所说的线程池的本质就是这个workers,也就是一个HashSet              workers.add(w);               int s = workers.size();
      if (s > largestPoolSize)
       largestPoolSize = s;
      workerAdded = true;
     }
    } finally {
     mainLock.unlock();
    }          //下面这个if语句中就是一个无限循环的去执行线程的start()方法
    if (workerAdded) {
     t.start();
     workerStarted = true;
    }
   }
  }return workerStarted;
 }

说出来你可能不信,我有点没看懂这里,因为最后的那个start()方法总感觉有点问题,但是说不上来,你们觉得这个start()方法之后,CPU来运行这个线程会执行哪个run()方法?是我们传进去的类的run()方法?还是worker的run()方法呢?

我们看看下面这两行代码,Worker构造器中的新建线程的代码就不截图了,我们把下面这几行代码变化一下:

Worker w = new Worker(firstTask);
final Thread t = w.thread;........t.start()

变化后:  

Worker w = new Worker(firstTask);//firstTask是我们传进去的实现了Runnable接口的类,但是Worker也实现了Runnable接口 

final Thread t = getThreadFactory().newThread(w

t.start()

看到没有,其实调用的是Worker的run()方法,然后在Worker的run()方法的内部又会进行很多处理,最后再去调用我们传进去的那个run()方法。5.总结  其实个人感觉深入到这里就差不多了,基本上就理解了线程池的流程,当然有兴趣的小伙伴可以继续深入看看Worker中的run()方法是怎么执行的,其实比较容易,主要是由很多对线程池很多状态啊,线程数量等判断可能会干扰我们的理解,如果后续有需要我们再慢慢深入,哈哈哈!  这一篇其实没说多少内容,就是让大家对线程池到底是个什么鬼有个最粗略的认识,其实本质就是一个HashSet<Worker>,然后把我们创建的Runnable()实例先给封装成Worker对象,其中Worker也是实现了Runnable接口,有点类似装饰者模式,哈哈!再之后就是将WOrker存到那个集合中,并且就调用start()方法调用worker的run()方法,然后最后可能就是调用我们传进去的那个run()方法了

(0)

相关推荐

  • java虚拟机中多线程总结

    我记得最开始接触多进程,多线程这一块的时候我不是怎么理解,为什么要有多线程啊?多线程到底是个什么鬼啊?我一个程序好好的就可以运行为什么要用到多线程啊?反正我是十分费解,即使过了很长时间我还是不是很懂,听别人说过也自己试过,但总是没有理解透彻: 时间过了很久感觉现在对多线程有了一点新的理解,我们还是从最基本的开始,顺便看看从jvm的角度看看多线程在jvm中是怎么分配内存的,顺便和前面的几篇内容串一下: 1.现实中的多线程 举个例子:假如你一个人在家,你现在听首歌5分钟,烧开水需要10分钟,玩一局游

  • java虚拟机多线程进阶篇总结

    1.线程池基本参数 以Executors.newFixedThreadPool()这种创建方式为例: 大家想象,假如你创建一个线程池,你想这个池子有些什么参数呢?首先这个池子必须要有一个最大值:然后还希望这个池子的线程数量有一个警戒线,到了这个警戒线的位置说明线程池暂时已经满了,如果这个时候还有人过来拿线程,我们就要把这些人抓起来扔到一个地方去让他们排队,告诉他们:请稍等,等我们的线程有空闲的时候再来处理你的事:再然后假如人排队的地方都满了,玛德,好多人,于是线程池就想办法东拼西凑又多搞出来了几

  • java开发Activiti进阶篇流程实例详解

    目录 1.流程实例 1.1 什么是流程实例 1.2 业务管理 1.3 流程实例的挂起和激活 1.3.1 全部流程挂起 1.3.2 单个实例挂起 1.流程实例 1.1 什么是流程实例 流程实例(ProcessInstance)代表流程定义的执行实例 一个流程实例包括了所有的运行节点,我们可以利用这个对象来了解当前流程实例的进度等信息 例如:用户或者程序安装流程定义的内容发起了一个流程,这个就是一个流程实例 1.2 业务管理 ​流程定义部署在Activiti后,我们就可以在系统中通过Activiti

  • java虚拟机学习高级篇

    还是继续说一下java虚拟机,为什么呢?因为我随意翻着别人的博客一不小心看到有关jvm的一点新的东西,挺有趣的,就按照我的理解分享一下: 还记得以前学过一首诗,"看成岭侧成峰,远近高低各不同",这一句诗的内在含义有的时候真的会让你猛然惊醒,进而如获至宝!的确,有的时候换一个角度看问题,你会发现不一样的世界. 我们平常学java的时候肯定涉及到了进程,多线程的概念,但是有没有想过操作系统也有进程和线程的概念,两者有关系吗?假如我们视角放高一点,以操作系统的角度看看一个java程序的运行,

  • java虚拟机学习笔记进阶篇

    上一节是把大概的流程给过了一遍,但是还有很多地方没有说到,后续的慢慢会涉及到,敬请期待! 这次我们说说垃圾收集器,又名gc,顾名思义,就是收集垃圾的容器,那什么是垃圾呢?在我们这里指的就是堆中那些没人要的对象. 1.垃圾收集器的由来 为什么要有垃圾收集器啊?不知道有没有想过这个问题,你说我运行一个程序要什么垃圾收集器啊? 随意看一下下面两行代码: User user = new User("root","123456") user = new User("

  • Java并发编程进阶之线程控制篇

    目录 一.线程的基本概念 1.并行和并发 2.进程和线程 二.线程的运行状态 三.线程操作实践 1.线程两种定义方法 2.启动线程 3.同时定义和启动线程 4.线程弹出与暂停 5.线程等待与唤醒 6.线程中断 一.线程的基本概念 1.并行和并发 并行:多个CPU核心同时工作,处理不同的任务. 并发:多个任务交替使用 CPU 核心工作,以提高 CPU 利用率. 2.进程和线程 进程:程序的一次执行.由操作系统创建并分配资源,执行一个单独的任务. 进程是系统进行资源分配和调度的独立单位,每个进程都有

  • Java中jqGrid 学习笔记整理——进阶篇(二)

    相关阅读: Java中jqGrid 学习笔记整理--进阶篇(一) 本篇开始正式与后台(java语言)进行数据交互,使用的平台为 JDK:java 1.8.0_71 myEclisp 2015 Stable 2.0 Apache Tomcat-8.0.30 Mysql 5.7 Navicat for mysql 11.2.5(mysql数据库管理工具) 一.数据库部分 1.创建数据库 使用Navicat for mysql创建数据库(使用其他工具或直接使用命令行暂不介绍) 2. 2.创建表 双击打

  • 老生常谈Java虚拟机垃圾回收机制(必看篇)

    在Java虚拟机中,对象和数组的内存都是在堆中分配的,垃圾收集器主要回收的内存就是再堆内存中.如果在Java程序运行过程中,动态创建的对象或者数组没有及时得到回收,持续积累,最终堆内存就会被占满,导致OOM. JVM提供了一种垃圾回收机制,简称GC机制.通过GC机制,能够在运行过程中将堆中的垃圾对象不断回收,从而保证程序的正常运行. 垃圾对象的判定 我们都知道,所谓"垃圾"对象,就是指我们在程序的运行过程中不再有用的对象,即不再存活的对象.那么怎么来判断堆中的对象是"垃圾&q

  • java虚拟机学习笔记基础篇

    1.前言(基于JDK1.7) 最近想把一些java基础的东西整理一下,但是又不知道从哪里开始!想了好久,还是从最基本的jvm开始吧!这一节就简单过一遍基础知识,后面慢慢深入... 水平有限,我自己也是很难把jvm将清楚的,我参考一本书<深入java虚拟机第二版>(版本比较老,其实很多大佬的博客都是参考的这本书的内容...) 所谓jvm,又名java虚拟机.我们平常写java程序的时候几乎是感觉不到jvm的存在的,我们只需要根据java规范去编写类,然后就可以运行程序了,当然只有我们程序出现bu

  • Java虚拟机JVM性能优化(一):JVM知识总结

    Java应用程序是运行在JVM上的,但是你对JVM技术了解吗?这篇文章(这个系列的第一部分)讲述了经典Java虚拟机是怎么样工作的,例如:Java一次编写的利弊,跨平台引擎,垃圾回收基础知识,经典的GC算法和编译优化.之后的文章会讲JVM性能优化,包括最新的JVM设计--支持当今高并发Java应用的性能和扩展. 如果你是一个开发人员,你肯定遇到过这样的特殊感觉,你突然灵光一现,所有的思路连接起来了,你能以一个新的视角来回想起你以前的想法.我个人很喜欢学习新知识带来的这种感觉.我已经有过很多次这样

随机推荐