大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
jvm结构原理,GC工作原理
Jvm结构:
Jvm主要包括四个部分:
1、类加载器(ClassLoad)
在JVM启动时或者在类运行时将需要的class加载到JVM中。
类加载时间与过程:
类从被加载到虚拟机内存开始,在到卸载出内存为止,正式生命周期包括了:加载,验证,准备,解析,初始化,使用和卸载7个阶段。其中验证、准备、解析这个三个步骤被统称为连接(linking)。
其中,加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的 ,类的加载过程必须按照这种顺序按部就班的“开始”(仅仅指的是开始,而非执行或者结束,因为这些阶段通常都是互相交叉的混合进行,通常会在一个阶段执行的过程中调用或者激活另一个阶段),而解析阶段则不一定(它在某些情况下可以在初始化阶段之后再开始,这是为了支持java语言的运行时绑定)
在以下几种情况下,会对未初始化的类进行初始化:
- 创建类的实例
- 对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化
- 当初始化一个类的时候,发现其父类没有被初始化,则需要先初始化父类
- 当虚拟机启动的时候,用户需要指定一个执行的主类,虚拟机会先初始化这个主类
类实例化和类初始化是两个不同概念:
类实例化:是指创建一个类的实例对象的过程
类初始化:是指类中各个类成员(被static修饰的成员变量)赋初始值的过程,是类生命周期的一个过程
ClassLoader的等级加载机制
Java默认提供的三个ClassLoader
BootStrap ClassLoader:被称为启动类加载机制,是Java类加载层次中最顶层的类加载器,负责加载JDK中核心类库。
Extension ClassLoader:被称为扩展类加载器,负责加载Java的扩展类库,Java虚拟机的实现会提供一个扩展目录,该类加载器在此目录里面查找并加载Java类
AppClassLoader:被称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。一版来说,Java应用的类都 是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
ClassLoader加载类的原理:
ClassLoader使用的是双亲委托机制来搜索加载类的,每一个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是组合关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它的ClassLoadre实例的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是有上之下一次检查的。首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没有加载到,则把任务转交给Extension ClassLoader试图加载,如果没有加载到,则转交给AppClassLoader进行加载,如果它也没有加载到话,则发挥给委托的发起者,有它到指定的文件系统或网络等URL中加载该类,如果都没有加载到此类,那么抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存中,最后返回这个类的内存中Class对象。
类的加载器双亲委托模型:
2、运行时数据区(内存区)
是在jvm运行的时候操作所分配的内存区。运行时内存区主要分为5个区域:
方法区(Methd Area):用于存储类结构信息的地方,包括常量池,静态变量,构造函数。虽然JVM规范把方法区描述为堆的一个逻辑部分,但它却有个别名(non-heap 非堆)
Java堆(Heap):存储java实例或者对象的地方。这块是GC的主要区域。从存储内容上可以看到Java堆和方法区是被java线程共享的。
Java栈(Stack):java栈总是和线程关联在一起,每当创建一个线程时,jvm就会为这个线程创建一个对应的java栈。在这个Java栈中又会包含多个帧栈,每运行一个方法就创建一个帧栈,由于存储局部变量,操作栈,方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个帧栈在Java栈中入栈和出栈的过程。所以java栈是私有。
程序计数器(PC Register):用于保存当前线程执行的内存地址。由于JVN程序是多线程执行的(线程轮转切换),所以为了保证线程切换回来,还能回复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的。
本地方法栈(Native Method Stack):和Java栈的作用差不多,只不过是为JVM使用到的native方法服务的。
3、执行引擎
负责执行class文件中包含的字节码指令
4、本地接口
主要是调用C或C++实现的本地方法及返回结果
JVM内存分配:
Java虚拟机是一次性分配一块较大的内存空间,然后每次new时都在该空间上进行分配和释放,减少了系统调用的次数,节省了一定的开销,这有点类似于内存池的概念。有了这块空间,如何进行分配和回收就跟GC机制有关系了。
Java一般内存申请有两种:静态内存和动态内存。很容易理解,编译时就能够确定的内存就是静态内存,即内存是固定的,系统一次性分配。比如int类型变量;动态内存分配就是在程序执行时才知道要分配的存储空间大小,比如java对象的内存空间。综上所述:java栈、程序计数器、本地方法栈都是线程私有的,线程生就生,线程灭就灭,栈中的栈帧随着方法的结束也会撤销,内存自然就跟着回收了。所以几个地方的内存分配和回收是确定的,不需要管。但是java堆和方法区则不一样,我们只有在程序运行期间才知道会创建哪些对象,所以这部分内存分配和回收是动态的。一般说的垃圾回收也是针对这部分的。
垃圾检测和回收算法
垃圾收集器一般必须完成两件事:检测出垃圾,回收垃圾。检测垃圾一般分为以下几种:
引用计数法:给对象增加一个引用计数器,每当有地方引用这个对象时,就在计数器上加1,引用失效就减1
可达性分析算法:以根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。这里的根集一般包括java堆中引用的对象,方法区常量池的引用对象。
总之,jvm在做垃圾回收的时候,会检查堆中的所有对象是否会被这些根集对象引用,不能够被引用的对象就会被垃圾收集器回收。一般回收有一下几种方法:
1、标记-清除(Mark-sweep):分为两个阶段,标记和清楚。标记所有需要回收的对象,然后统一回收这个是最基础的算法,后续的收集算法 都是基于这个算法扩展的。
不足:效率低;标记清楚之后会产生大量的碎片。
2、复制(copying): 此算法把内存空间划分为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前区域,把正在使用中的对象复制到另外区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去还能进行相应的内存整理,不会出现“内存碎片”问题。当然,此算法的缺点也是很明显,就是需要双倍内存。
3、标记-整理(Mark-Compact):此算法结合了“标记-清除”和“复制”两个算法的有点。也是两个阶段,第一阶段从根节点开始标记所被引用的对象。第二阶段遍历整个堆,把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“复制”算法的空间问题
4、分代收集算法:这是当前商业虚拟机常用的垃圾收集算法。分代的垃圾回收策率,是基于这样一个事实:不同的对象的生命周期不一样的。因此,不同生命周期的对象采取不同的收集方式,以便提高回收效率。
为什么要运用分代垃圾回收策率?在java程序运行的过程中,会产生大量的对象,因每个对象所能承担的职责和功能不同,所以也有着不同的生命周期。有的对象生命周期较长,有的对象生命周期较短。试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,那么消耗的时间相对比较长,而对于存活时间较长的对象进行扫描的工作是徒劳的。因此就引入了分治思想,所以分治思想就是因地制宜,将对象进行代划分,把不同的生命周期的对象放在不同的代上使用不同的垃圾回收方法。
如果划分?将对象按其生命周期的不同划分成:年轻代(Young Generation)、老年代(Old Generation)、持久代(Permanent Generation)。其中持久代主要存放的是类信息,所以与java对象的回收关系不大,与回收信息相关的是年轻代,老年代。
年轻代:是所有新对象产生的地方。年轻代被分为3个部分:Ender(出生)区和两个Survivor(幸存者)区(From和To)。当Ender区被对象填满时,就会执行Minor GC。并把所有存活下来的对象转移到其中一个Survivor区(假设为from区)。Minor GC同样会检查存活下来的对象,并把他们转移到另一个Survivor区(假设为to区)。这样在一段时间内总会有一个空的Survivor区。经过多次GC后,仍然存活下来的对象会被转移到老年代内存空间。
年老带:在年轻代经过N次回收的仍然没有被清除的对象会放到老年代,可以说它们是久经沙场而不亡的一代,都是生命周期较长的对象。对于老年代和永久代,就不能再采用年轻代中那样搬移腾挪的回收算法,因为那些对于这些回收战场上的老兵来说是小儿科,通常会在老年代内存被占满时将会触发Full GC,回收整个堆内存。
持久代:用于存放静态文件,比如Java类,方法等。持久代对垃圾回收没有显著的影响。
我这里之所以最后讲分代,是因为分代里涉及了前面几种算法。年轻代:涉及了复制算法;年老代:涉及了“标记–整理(Mark-Sweep)”的算法。
Java线程池
java中的ThreadPoolExecutor类
ThreadPoolExecutor类继承了AbstractExecutorService类,并提供了四个构造器。构造器中分别有一下参数:
- corePoolSize:核心池的大小,在创建线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法来预创建线程。即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
- maximumPoolSize:线程池最大的线程数,表示在线程池中最多能创建多少个线程;
- keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize。即当线程池中的线程数大于corePoolSize时,如果一个线程空闲时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,知道线程池中的线程数为0。
- unit:参数keepAliveTime的时间单位,有7中取值,在TimeUnit类中有7中静态属性:
- TimeUnit.DAYS; //天
- TimeUnit.HOURS; //小时
- TimeUnit.MINUTES; //分钟
- TimeUnit.SECONDS; //秒
- TimeUnit.MILLISECONDS; //毫秒
- TimeUnit.MICROSECONDS; //微妙
- TimeUnit.NANOSECONDS; //纳秒
- workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一版来说阻塞队列有以下几种:
- ArrayBlockingQueue
- LinkedBlockingQueue
- SynchronousQueue
ArrayBlockingQueue使用较少,一使用LinkedBlokingQueue和SynchronousQueue。线程池排队策率与BlockingQueue有关
- threadFactory:线程工厂,主要用来创建线程;
- hander:表示当拒绝处理任务时的策率,有一下四种取值:
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常
- ThreadPoolExecutor.DiscardOlddestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:有调用线程处理该任务
ThreadPoolExecutor继承了AbstractExecutorService抽象类,AbstractExecutorService抽象类实现了ExecutorService接口,ExecutorService接口继承了Executor接口。
Executor:是一个顶层接口,在它里面只有一个方法execute(Runnable),返回值为void,参数为Runnable类型,其实就是为了执行传进来的任务;
然后ExecutorService接口继承了Executor接口,并生命了一些方法:submit、invokeAll、invokeAny以及shutDown等。
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService接口中所有的方法;
然后ThreadPoolExecutor继承了抽象类AbstractExecutorService;
在ThreadPoolExecutor类中有几个非常重要的方法:
- execute():实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交线程池去执行。
- submit():此方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和executor()方法不同,它能够返回任务执行的结果,去看submit()方法的方法实现,会发现它实际上还是调用的execute()方法,只不过它利用了Futrure来获取任务的执行结果
- shutDown():是用来关闭线程
- shutDownNow():是用来关闭所有线程
深入剖析线程池实现的原理图
深入解析一下线程池的具体实现原理,将从下面几个方面讲解:
- 线程池状态
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
volatile in runSize;
static final int Running = 0;
static final int shutdown = 1;
static final int stop = 2;
static final int terminated=3;
runSize:表示当前线程池状态,它是一个volatile变量用来保证线程之间的可见性
下面几个static final int 变量表示runSize可能的几个取值
当创建线程池后,初始时,线程池处于Running状态;
如果调用了shutdown()方法,则线程池处于shutdown状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
如果滴啊用shutdownNow()方法,则线程池处于stop状态,此时线程池不能接受新的任务,并且会尝试终止正在执行的任务;
当线程池处于shutdown或者stop状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为terminated状态
- 任务的执行
ThreadPoolExecutor类中其他的一些比较重要的成员变量:
private
final
BlockingQueue<Runnable> workQueue;
//
任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();//
线程池的主要状态锁,对线程池状态(比如线程池大小等)的改变都要使用这个锁
private
final
HashSet<Worker> workers =
new
HashSet<Worker>();
//
用来存放工作集
private
volatile
long
keepAliveTime;
//
线程存货时间
private
volatile
boolean
allowCoreThreadTimeOut;
//
是否允许为核心线程设置存活时间
private
volatile
int
corePoolSize;
//
核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private
volatile
int
maximumPoolSize;
//
线程池最大能容忍的线程数
private
volatile
int
poolSize;
//
线程池中当前的线程数
private
volatile
RejectedExecutionHandler handler;
//
任务拒绝策略
private
volatile
ThreadFactory threadFactory;
//
线程工厂,用来创建线程
private
int
largestPoolSize;
//
用来记录线程池中曾经出现过的最大线程数
private
long
completedTaskCount;
//
用来记录已经执行完毕的任务个数
任务提交给线程池之后处理的策略,主要有一下四点:
如果当前线程池中线程数目小于corePoolSize,则每来一个线程,就会创建一个线程去执行这个任务;
如果当前线程池中线程数据大于等于corePoolSize,则没来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一版来说是任务缓存队列已满),则会尝试尝试创建新的线程去执行这个任务;
如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策率进行处理;
如果线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数据不大于corePoolSize;如果允许核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止
- 线程池中的线程初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。在实际中如果需要线程池创建之后立即创建线程,可以通过下面两种方法:
prestartCoreThread():
初始化一个核心线程
prestartAllCoreThread():
初始化所有核心线程
- 任务缓存队列及排队策略
任务缓存队列,即workQueue,它用来存放等待执行的任务
workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:
ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须制定大小
LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列的大小,那么默认为Integer.MAX_VALUE
synchronousQueue:这个队列比较特殊,他不会保存提交的任务,而是将直接新建的一个线程来执行新的任务。
- 任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常通过一下四种策略:
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常
- ThreadPoolExecutor.DiscardOlddestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:有调用线程处理该任务
- 线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别为shutDown()和shutDownNow(),其中:
shutDown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会执行新的任务
shutDownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
- 线程池容量的动态调动
ThreadPoolExecutor提供了动态调用线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
setCorePool():设置核心池大小
setMaximumPoolSize():设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务
使用示例
package ThreadPoolExecutor;
public class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println(“正在执行task”+taskNum);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(“task” + taskNum + “执行完毕“);
}
}
package ThreadPoolExecutor;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i = 0 ; i < 15 ; i ++){
MyTask mytask = new MyTask(i);
executor.execute(mytask);
System.out.println(“线程池中线程数目:“+executor.getPoolSize()+“,队列中等待执行的任务数目:“+
executor.getQueue().size()+“,已执行玩别的任务数目:“+executor.getCompletedTaskCount());
}
executor.shutdown();
}
}
执行结果:
正在执行task0
线程池中线程数目:1,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
正在执行task1
线程池中线程数目:3,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
正在执行task2
线程池中线程数目:4,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
正在执行task3
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
正在执行task4
线程池中线程数目:5,队列中等待执行的任务数目:1,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:2,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:3,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:4,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:6,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
正在执行task10
正在执行task11
线程池中线程数目:7,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:8,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:9,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
正在执行task13
正在执行task12
线程池中线程数目:10,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
正在执行task14
task0执行完毕
正在执行task5
task1执行完毕
正在执行task6
task2执行完毕
正在执行task7
task3执行完毕
正在执行task8
task4执行完毕
正在执行task9
task11执行完毕
task10执行完毕
task13执行完毕
task14执行完毕
task12执行完毕
task5执行完毕
task6执行完毕
task9执行完毕
task8执行完毕
task7执行完毕
从执行结果可以看出,当线程池中的线程的数目大于5时,便将任务放入缓存队列里面,当任务缓存队列满了之后,便创建新的线程。如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了;
在java doc中,并不建议直接使用ThreadPoolExecutor直接创建线程池,而是使用Executor类中提供的几个静态方法来创建线程池:
Executor.newCachedThread()://创建一个缓冲池,缓冲池大小为Integer.MAX_VALUE
Executor.newSingleThreadExecutor()://创建容量为1的缓冲池
Executor.newFixedThreadPool()://创建固定容量的缓冲池
以下是这个三个静态方法的具体实现:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
从它们的具体实现来看,他们实际上也是调用了ThreadPoolExecutor,只不过参数都已经配置好了,
newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
newSingleThreadPool将corePoolSize和maximumPoolSize的值都设置为1,也使用的LinkedBlockingQueue;
newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程
实际工作中,如果Executors提供的三种静态方法如果能够满足要求,就尽量使用它提供的三个方法,因为自己手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数据量来进行配置。如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写
如何合理配置线程池的大小
一般需要根据任务的类型来配置线程池大小;
如果是CPU密集型任务,就需要压榨CPU,参考值可以设为Ncpu+1
如果是IO密集型任务,参考值可以设为2*Ncpu
当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。
Java四种线程池
线程池的优势:
- 重用存在的线程,减少对象的创建,消亡的内存开销,性能佳
- 可有效控制最大并发线程数,提高系统资源使用率,同时避免过多资源竞争,避免堵塞
- 提供定时执行,定期执行,单线程,并发数控制功能。
Executors提供的四种线程池:
- newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需求,可灵活回收空线程,若无可回收的线程,则新建线程
package ThreadPoolExecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for(int i = 0 ; i < 10 ; i++ ){
final int index = 1;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable(){
@Override
public void run() {
System.out.println(index);
}
});
}
cachedThreadPool.shutdown();//立即终止线程池
}
}
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不是每次新增线程
- newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
package ThreadPoolExecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for(int i = 0 ; i < 10 ; i ++){
final int index = 1;
fixedThreadPool.execute(new Runnable(){
@Override
public void run() {
System.out.println(index);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
fixedThreadPool.shutdown();
}
}
因为线程池大小为3,每个任务输出index后sleep2秒,所以每两秒打印三个数字;
- newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行
package ThreadPoolExecutor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable(){
@Override
public void run() {
System.out.println(“这是一个延迟线程“);
}
}, 3, TimeUnit.SECONDS);// 表示延迟3秒执行
scheduledThreadPool.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
System.out.println(“delay 1 seconds, and excute every 3 seconds”);
}
}, 1, 2, TimeUnit.SECONDS);// 表示延迟1秒后每3秒执行一次
//scheduledThreadPool.shutdown();
}
}
- newSingleThreadPool:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定的顺序(FIFO,LIFO)执行
package ThreadPoolExecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class singleThreadExecutor {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for(int i = 0 ; i < 10; i++){
final int index = 1;
singleThreadExecutor.execute(new Runnable(){
@Override
public void run() {
System.out.println(index);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
singleThreadExecutor.shutdown();
}
}
结果依次输出,相当于顺序执行各个任务
线程池的作用:
线程池是为了限制系统中执行线程的数量
根据系统环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其它线程排队等候。一个任务执行完毕,再从队列中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
为什么要用线程池:
- 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
- 可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,增加服务器的压力(每个线程大约需要1MB的内存,线程开的越多,消耗内存越大,最后导致司机);
Java里面线程池的顶级接口是Executor,但是严格意义上将Executor并不是一个线程池,而是一个执行线程的工具。真正的线程池接口是ExecutorService.
比较重要的几个类:
Executor:真正的线程池接口
ScheduledExecutorService:能和Timer/TimerTask类似,解决那些需要任务重复执行的问题
ThreadPoolService:ExecutorService默认实现类
ScheduledExecutorPoolExecutor:继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
要配置一个线程池是复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是最优的,因此在Executors类里面提供了一个静态工厂,生成一些常用的线程池。
- newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程在串行执行所有的任务。如果这个唯一的线程因为异常信息中断,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
- newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么回收空闲(60秒不执行)的线程,当任务增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
- newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
一个任务通过execute(Runnable)方法被添加到线程池,任务就是一个Runnable类型的对象,任务的执行方法就是Runnable类型对象的run()方法
当一个任务通过execute(Runnable)方法欲添加到线程池时:
如果此时线程池中的数据小于corePoolSize,即使线程池中的线程处于空闲状态,也要创建新的线程来处理被添加的任务。
如果此时线程池中的数据等于 corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列
如果此时线程池中的数据大于 corePoolSize,缓冲队列workQueue已满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
如果此时线程池中的数量大于 corePoolSize,缓冲队列workQueue已满,并且线程池中的数量等于maximumPoolSize,那么通过handler所指定的策略来处理此任务。
处理任务的优先级:
核心线程corePoolSize,任务队列workQueue,最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/171352.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...