线程池,既然是个池子里面肯定就装很多线程。

如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁 线程,如此一来会大大降低系统的效率。可能出现服务器在为每个请求创建新线程和销毁线 程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。因此Java中提供线程池对线程进行统一的管理使用。

线程池可以让多个任务重用线程。减少线程创建,消亡的开销,提高性能。当任务达到不需要等待线程创建便可立即执行。提高线程的可管理性。使用线程池进行统一的分配,调优和监控。

Executor框架体系:

Executor接口是线程池框架中最顶级的接口,定义了一个用于执行Runnable的execute方法

体系简要类图:

ExecutorService也是一个重要的接口,其中也定义了一系列重要的方法:

  1. submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future 对象
  2. shutdown():在完成已提交的任务后封闭办事,不再接管新任务
  3. shutdownNow():停止所有正在履行的任务并封闭办事。
  4. isTerminated():测试是否所有任务都履行完毕了。
  5. isShutdown():测试是否该ExecutorService已被关闭。.
  6. invokeXXX(task,...):执行给定任务

从类图中可以看到一个很重要的实现类就是ThreadPoolExecutor,我们通过这个类看下线程池中比较重要的一些属性。

ctl 是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它包含两部分的信息: 线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),这里可以看到,使用了Integer类型来保存,高3位保存runState,低29位保存
workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位减1(29个1),这个常量表示workerCount的上限值,大约是5.3亿。
线程池五种状态:

RUNNING:ctl高三位111,可以接受新任务,并且处理已经添加的任务。线程池的初始化状态就是running。

SHUTDOWN:ctl高三位000,不接受新任务,但是可以处理已经添加的任务。调用shutdown()方法,由running变成shutdown

STOP:ctl高三位001,不接受新任务,不处理已添加的任务,并且还会中断正在处理的任务。调用shutdownNow()方法,由running/shutdown变成stop

TIDYING:ctl高三位010,当所有的任务已终止,ctl记录的任务数量为0,线程池会变为tidying 状态。当线程池变为tidying 状态时,会执行钩子函数terminated()。terminated()在 ThreadPoolExecutor类中是空的,若用户想在线程池变为tidying 时,进行其他处理;可以通过重写terminated()。线程池为shutdown状态,并且阻塞队列是空的,并且执行的任务也是空就会由shutdown变成tidying;stop状态时,线程池中任务为空也会变成tidying

TERMINATED:ctl高三位011,线程池彻底凉凉了

当线程池处于tidying状态并且执行完了terminated()方法,就会由tidying变成terminated

构造方法:

corePoolSize:线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当 前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到 阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会 提前创建并启动所有核心线程。

maximumPoolSize:线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;

keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime。

unit:keepAliveTime的单位。

workQueue:用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:

  1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;

   2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,

  3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。

   4、priorityBlockingQuene:具有优先级的无界阻塞队列;

threadFactory :ThreadFactory,用来创建新线程。默认使用 Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。

Handler:线程池的拒绝策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

  1、AbortPolicy:直接抛出异常,默认策略;

  2、CallerRunsPolicy:用调用者所在的线程来执行任务;

  3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

  4、DiscardPolicy:直接丢弃任务;

上面的4种策略都是ThreadPoolExecutor的内部类。 当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义策略。

通过execute或者submit向线程池提交任务。任务提交的时候先提交给核心线程(corePoolSize);如果核心线程满了,就将任务放到workQueue里面去排队等待;如果队列也满了(取决用的什么队列,以及设置的大小),就会将新进来的任务提交给非核心线程,非核心线程数量等于maximumPoolSize - corePoolSize,非核心线程使用之后会被回收。如果非核心线程也满了,那么就执行相应的拒绝策略RejectedExecutionHandler。

 

源码解析:

1.execute方法:

2. 可以看到关键方法是addWork方法,可以看到在addWork方法先会进行一系列判断,如果都通过了,才会进行任务的创建

3.Worker内部类,这类是继承了AbstractQueuedSynchronizer并且实现了Runable接口,其中还有两个重要属性一个是Thread, 一个是firstTask,初始化的时候会吧AQS中的state字段设置-1,后面允许中断会将这个值修改。

会通过线程工厂创建一个线程和当前的worker绑定,创建线程的runable接口对象就是work本身。worker重写的run方法实际调用了线程池的runWorker方法

4. 回到addWorker方法,创建完了worker,可以就可以获取到绑定的线程了,将worker添加到工作线程的集合中去。然后调用对象绑定线程的start方法,实际上会调用到worker的run方法,进而调用线程池的runWorker方法

5. runWorker 方法中会去worker中取任务,如果firstTask空,就去队列中取,因为之前在addwork的时候有些场景传入的firstWork是null。从队列取不到任务了,也就是getTask返回null了,结束while循环,调用processWorkerExit方法移除任务,处理最终的一些状态转换

就是说通过线程调用worker的run方法,然后借助worker里面再来调用我们传入线程池中的任务的run方法。所以说其实是起了一些线程,然后调用run方法一直尝试去取任务,取到之后手动调用的run方法执行任务。

JUC(4)---java线程池原理及源码分析的更多相关文章

  1. JAVA线程池原理与源码分析

    1.线程池常用接口介绍 1.1.Executor public interface Executor { void execute(Runnable command); } 执行提交的Runnable ...

  2. Java ThreadPoolExecutor线程池原理及源码分析

    一.源码分析(基于JDK1.6) ThreadExecutorPool是使用最多的线程池组件,了解它的原始资料最好是从从设计者(Doug Lea)的口中知道它的来龙去脉.在Jdk1.6中,Thread ...

  3. Java线程池ThreadPoolExector的源码分析

    前言:线程是我们在学习java过程中非常重要的也是绕不开的一个知识点,它的重要程度可以说是java的核心之一,线程具有不可轻视的作用,对于我们提高程序的运行效率.压榨CPU处理能力.多条线路同时运行等 ...

  4. Java线程池ThreadPoolExecutor类源码分析

    前面我们在java线程池ThreadPoolExecutor类使用详解中对ThreadPoolExector线程池类的使用进行了详细阐述,这篇文章我们对其具体的源码进行一下分析和总结: 首先我们看下T ...

  5. Java线程池及其底层源码实现分析

    1.相关类 Executors  ExecutorService   Callable   ThreadPool     Future 2.相关接口 Executor Executor接口的使用: p ...

  6. 详解Java线程池的ctl(线程池控制状态)【源码分析】

    0.综述 ctl 是线程池源码中常常用到的一个变量. 它的主要作用是记录线程池的生命周期状态和当前工作的线程数. 作者通过巧妙的设计,将一个整型变量按二进制位分成两部分,分别表示两个信息. 1.声明与 ...

  7. 线程池:ThreadPoolExecutor源码解读

    目录 1 带着问题去阅读 1.1 线程池的线程复用原理 1.2 线程池如何管理线程 1.3 线程池配置的重要参数 1.4 shutdown()和shutdownNow()区别 1.5 线程池中的两个锁 ...

  8. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  9. ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap实现原理 ConcurrentHashMap源码分析 总结 ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对Ha ...

随机推荐

  1. USACO 2.1 海明码 Hamming Codes (模拟+位运算+黑科技__builtin_popcount(n))

    题目描述 给出 N,B 和 D,要求找出 N 个由0或1组成的编码(1 <= N <= 64),每个编码有 B 位(1 <= B <= 8),使得两两编码之间至少有 D 个单位 ...

  2. 2019 ICPC 银川网络赛 H. Fight Against Monsters

    It is my great honour to introduce myself to you here. My name is Aloysius Benjy Cobweb Dartagnan Eg ...

  3. MySQL 增删改查(单表)

    1.sql 新增语句 表中插入数据 insert into + 表名 values(字段1value1,字段2value1,字段3value1),(字段1value2,字段2value2,字段3val ...

  4. D. Sequence Sorting dp

    D. Sequence Sorting 题目大意:给你一个序列,有一种操作就是对所有相同的数可以挪到最前面,也可以挪到最后面,问最少操作次数. 首先,对于很多个相同的数,可以缩成两个位置,一个是就是这 ...

  5. E. A Simple Task

    E. A Simple Task 这个题目的意思是 给你一个由小写字母组成的字符串,有两种操作 i j k 如果k==1 那么就是就把i 到 j 的这个区间非递减排序. i j k如果k==2 那么就 ...

  6. github使用命令

    创建本地库,提交,和绑定github ,上传代码 git init git add README.md git commit -m "first commit" git remot ...

  7. Maven安装本地jar包到本地仓库

    Maven 安装 JAR 包到本地仓库的命令是: mvn install:install-file -Dfile=jar包的位置 -DgroupId=上面的groupId -DartifactId=上 ...

  8. Go语言入门教程系列——函数、循环与分支

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Golang专题的第四篇,这一篇文章将会介绍golang当中的函数.循环以及选择判断的具体用法. 函数 在之前的文章当中其实我们已经接 ...

  9. Redis实现分布式锁(设计模式应用实战)

    笔者看过网络上各种各样使用redis实现分布式锁的代码,要么错误,要么片段化,没有一个完整的例子,借这个周末给大家总结一下redis实现分布式锁的两种机制 自旋锁和排他锁 鉴于实现锁的方式不同,那么这 ...

  10. 字节码编程,Javassist篇三《使用Javassist在运行时重新加载类「替换原方法输出不一样的结果」》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 通过前面两篇 javassist 的基本内容,大体介绍了:类池(ClassPool) ...