java中高级面试题总结(全面)_java面试题大全

java中高级面试题总结(全面)_java面试题大全jvm结构原理,GC工作原理Jvm结构:Jvm主要包括四个部分:1、类加载器(ClassLoad)在JVM启动时或者在类运行时将需要的class加载到JVM中。类加载时间与过程:类从被加载到虚拟机内存开始,在到卸载出内存为止,正式生命周期包括了:加载,验证,准备,解析,初始化,使用和卸载7个阶段。其中验证、准备、解析这个三个步…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

jvm结构原理,GC工作原理

Jvm结构:

           java中高级面试题总结(全面)_java面试题大全        java中高级面试题总结(全面)_java面试题大全

Jvm主要包括四个部分:

1、类加载器(ClassLoad)

在JVM启动时或者在类运行时将需要的class加载到JVM中。

 

类加载时间与过程:

类从被加载到虚拟机内存开始,在到卸载出内存为止,正式生命周期包括了:加载,验证,准备,解析,初始化,使用和卸载7个阶段。其中验证、准备、解析这个三个步骤被统称为连接(linking)。

                                       java中高级面试题总结(全面)_java面试题大全

其中,加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的   ,类的加载过程必须按照这种顺序按部就班的“开始”(仅仅指的是开始,而非执行或者结束,因为这些阶段通常都是互相交叉的混合进行,通常会在一个阶段执行的过程中调用或者激活另一个阶段),而解析阶段则不一定(它在某些情况下可以在初始化阶段之后再开始,这是为了支持java语言的运行时绑定)

 

在以下几种情况下,会对未初始化的类进行初始化:

  1. 创建类的实例
  2. 对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化
  3. 当初始化一个类的时候,发现其父类没有被初始化,则需要先初始化父类
  4. 当虚拟机启动的时候,用户需要指定一个执行的主类,虚拟机会先初始化这个主类

 

类实例化和类初始化是两个不同概念:

类实例化:是指创建一个类的实例对象的过程

类初始化:是指类中各个类成员(被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对象。

类的加载器双亲委托模型:

                                                               java中高级面试题总结(全面)_java面试题大全

 

2、运行时数据区(内存区)

是在jvm运行的时候操作所分配的内存区。运行时内存区主要分为5个区域:

                                                                   java中高级面试题总结(全面)_java面试题大全

方法区(Methd Area):用于存储类结构信息的地方,包括常量池,静态变量,构造函数。虽然JVM规范把方法区描述为堆的一个逻辑部分,但它却有个别名(non-heap 非堆)

                                                       java中高级面试题总结(全面)_java面试题大全

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、本地接口

主要是调用CC++实现的本地方法及返回结果

 

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类,方法等。持久代对垃圾回收没有显著的影响。

                                                                         java中高级面试题总结(全面)_java面试题大全

我这里之所以最后讲分代,是因为分代里涉及了前面几种算法。年轻代:涉及了复制算法;年老代:涉及了标记整理(Mark-Sweep的算法。

 

Java线程池

java中的ThreadPoolExecutor类

ThreadPoolExecutor类继承了AbstractExecutorService类,并提供了四个构造器。构造器中分别有一下参数:

  1. corePoolSize:核心池的大小,在创建线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法来预创建线程。即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
  2. maximumPoolSize:线程池最大的线程数,表示在线程池中最多能创建多少个线程;
  3. keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize。即当线程池中的线程数大于corePoolSize时,如果一个线程空闲时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,知道线程池中的线程数为0。
  4. unit:参数keepAliveTime的时间单位,有7中取值,在TimeUnit类中有7中静态属性:
    1. TimeUnit.DAYS;               //天
    2. TimeUnit.HOURS;             //小时
    3. TimeUnit.MINUTES;           //分钟
    4. TimeUnit.SECONDS;           //秒
    5. TimeUnit.MILLISECONDS;      //毫秒
    6. TimeUnit.MICROSECONDS;      //微妙
    7. TimeUnit.NANOSECONDS;       //纳秒
  5. workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一版来说阻塞队列有以下几种:
    1. ArrayBlockingQueue
    2. LinkedBlockingQueue
    3. SynchronousQueue

ArrayBlockingQueue使用较少,一使用LinkedBlokingQueue和SynchronousQueue。线程池排队策率与BlockingQueue有关

  1. threadFactory:线程工厂,主要用来创建线程;
  2. hander:表示当拒绝处理任务时的策率,有一下四种取值:
    1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
    2. ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常
    3. ThreadPoolExecutor.DiscardOlddestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    4. 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类中有几个非常重要的方法:

  1. execute():实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交线程池去执行。
  2. submit():此方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和executor()方法不同,它能够返回任务执行的结果,去看submit()方法的方法实现,会发现它实际上还是调用的execute()方法,只不过它利用了Futrure来获取任务的执行结果
  3. shutDown():是用来关闭线程
  4. shutDownNow():是用来关闭所有线程

 

深入剖析线程池实现的原理图

深入解析一下线程池的具体实现原理,将从下面几个方面讲解:

  1. 线程池状态

在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状态

 

  1. 任务的执行

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,线程也会被终止

 

  1. 线程池中的线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。在实际中如果需要线程池创建之后立即创建线程,可以通过下面两种方法:

prestartCoreThread():初始化一个核心线程

prestartAllCoreThread():初始化所有核心线程

 

  1. 任务缓存队列及排队策略

任务缓存队列,即workQueue,它用来存放等待执行的任务

workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须制定大小

LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列的大小,那么默认为Integer.MAX_VALUE

synchronousQueue:这个队列比较特殊,他不会保存提交的任务,而是将直接新建的一个线程来执行新的任务。

  1. 任务拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常通过一下四种策略:

  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
  2. ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常
  3. ThreadPoolExecutor.DiscardOlddestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  4. ThreadPoolExecutor.CallerRunsPolicy:有调用线程处理该任务

 

  1. 线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别为shutDown()和shutDownNow(),其中:

shutDown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会执行新的任务

shutDownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

  1. 线程池容量的动态调动

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四种线程池

线程池的优势:

  1. 重用存在的线程,减少对象的创建,消亡的内存开销,性能佳
  2. 可有效控制最大并发线程数,提高系统资源使用率,同时避免过多资源竞争,避免堵塞
  3. 提供定时执行,定期执行,单线程,并发数控制功能。

Executors提供的四种线程池:

  1. 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();//立即终止线程池

         }

}

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不是每次新增线程

 

  1. 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秒,所以每两秒打印三个数字;

 

  1. 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();

         }

}

 

  1. 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();

  }

}

结果依次输出,相当于顺序执行各个任务

 

线程池的作用:

线程池是为了限制系统中执行线程的数量

         根据系统环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其它线程排队等候。一个任务执行完毕,再从队列中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

         为什么要用线程池:

  1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
  2. 可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,增加服务器的压力(每个线程大约需要1MB的内存,线程开的越多,消耗内存越大,最后导致司机);

Java里面线程池的顶级接口是Executor,但是严格意义上将Executor并不是一个线程池,而是一个执行线程的工具。真正的线程池接口是ExecutorService.

         比较重要的几个类:

         Executor:真正的线程池接口

         ScheduledExecutorService:能和Timer/TimerTask类似,解决那些需要任务重复执行的问题

         ThreadPoolService:ExecutorService默认实现类

         ScheduledExecutorPoolExecutor:继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

要配置一个线程池是复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是最优的,因此在Executors类里面提供了一个静态工厂,生成一些常用的线程池。

 

  1. newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程在串行执行所有的任务。如果这个唯一的线程因为异常信息中断,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

  1. newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程

  1. newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么回收空闲(60秒不执行)的线程,当任务增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

  1. 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账号...

(0)


相关推荐

  • mybatis拦截器修改sql_javaweb拦截器是什么

    mybatis拦截器修改sql_javaweb拦截器是什么/***Copyright2009-2016theoriginalauthororauthors.**LicensedundertheApacheLicense,Version2.0(the”License”);*youmaynotusethisfileexceptincompliancewiththeLicense.*…

  • Response.Flush()的用处

    Response.Flush()的用处Response.Flush()的作用是将缓冲信息输出到页面。比如我们在点击一个按钮后,执行多个任务,每个任务执行成功后都有一个Response.Write(‘成功信息’)。如果我们在Respons

  • 字符串分割与拼接「建议收藏」

    字符串分割与拼接「建议收藏」.字符串分割与拼接//@””   空的字符串对象     ——-分割     NSString * ptr = @”I am a man”;     NSArray * array = [ptr componentsSeparatedByString:@” “];//将字符串整体作为分割条件 返回值为NSArray不可变数组     NSMutableArray * array1 = 

  • 老鸟的Python新手教程

    老鸟的Python新手教程

    2021年12月10日
  • 视频列表

    视频列表

    2021年10月20日
  • vue 使用 axios 上传文件 — FormData

    vue 使用 axios 上传文件 — FormData    在此主要介绍如何使用formData对象上传单文件和多文件,FormData就是XMLHttpRequestLevel2新增的一个对象,利用它来提交表单、模拟表单提交,当然最大的优势就是可以上传二进制文件。    过多介绍不说,过多的煽情语句不说,直接来来干活,希望对广大博友有所帮助。也希望各位大神不吝赐教一:前台上传文件的表单和响应函数&lt;!–文件上传…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号