【Java开发岗面试】八股文—Java基础集合多线程

声明:

  1. 背景:本人为24届双非硕校招生,已经完整经历了一次秋招,拿到了三个offer。
  2. 本专题旨在分享自己的一些Java开发岗面试经验(主要是校招),包括我自己总结的八股文、算法、项目介绍、HR面和面试技巧等等,如有建议,可以友好指出,感谢,我也会不断完善。
  3. 想了解我个人情况的,可以关注我的B站账号:东瓜Lee

文章目录

      • Java基础
      • Java集合
      • 多线程(并发编程)

Java基础

  1. 面向对象的特征有哪些方面?
    1. 封装:合理隐藏、合理暴露
    2. 继承:单继承、子类可以继承父类的成员变量和方法,自己也可以追加内容,或者重写父类的方法、以实现代码复用
    3. 多态:
      1. 对象多态、行为多态
      2. 必须满足:
        1. 必须在继承体系下、子类需要对父类的方法进行重写、通过父类的引用调用重写方法
        2. 在代码运行时,当传递不同类对象时,会调用对应类中的方法。
    4. 抽象:抽象就是找出一些事物的相似和共性之处,然后将这些事物归为一个抽象类,这个类只考虑这些事物的相似和共性之处
      1. 抽象类不能被实例化
      2. 抽象类中可以有抽象方法和实例方法
      3. 抽象类的子类必须实现父类的全部抽象方法,否则也要定义为抽象类

Java和C++面向对象的区别?

  1. Java单继承、C++支持多继承
  2. Java有根基类Object、C++没有
  3. Java的访问修饰符是private、default、protected、public,C++没有default
  4. Java中没有析构函数这个概念,C++有析构函数(销毁对象的时候调用的)
  5. Java中的对象可以被GC回收,C++需要手动的释放空间

  1. public,private,protected,default的区别

    img

    1. public表示公开的,不管在哪里都可以直接访问
    2. protected表示受保护的,相比于public,用它修饰的变量或者方法不能在任意包的任意类里面被访问
    3. default表示默认的、缺省的,也可以称它为包访问,它只能在同一个包有效,其他包里面就不能直接访问了
    4. private表示私有的,访问范围最小,只能在本类中被访问

  1. String是最基本的数据类型吗?
    1. Java的八种基本数据类型:
      1. boolean
      2. byte、short、int、long
      3. float、double
      4. char
    2. 在Java中,整数默认是int、浮点数默认是double
    3. String属于引用数据类型

  1. 基本数据类型vs包装类

    1. 为了更好的实现面向对象的思想,引出了8种基本数据类型对应的8种包装类:

      1. Boolean
      2. Byte、Short、Integer、Long
      3. Float、Double
      4. character

      它们均继承自抽象类Number

    2. 自动装箱和拆箱:

      1. 装箱:将基本数据类型自动转换为对应的包装类型,比如基本数据类型int直接赋值给Integer,就会实现自动装箱
      2. 拆箱:将包装类型自动转换为对应的基本数据类型,比如将Integer对象直接赋值给int变量,就会实现自动拆箱
    3. int和Integer的区别(其他也一样)

      1. int是基本数据类型、Integer是引用类型、对象类型
      2. int的默认值是0,Integer的默认值是null
      3. Integer不能直接做运算,运算的时候会自动拆箱为int进行运算
    4. 注意:

      1. 无论如何,Integer与new Integer不会相等。不会经历拆箱过程,因为它们存放内存的位置不一样。
      2. (非new出来的)Integer的值在-128~127之间,就是从高速缓冲返回实例,如果值相同,直接用==号比较是true,说明是同一个对象,如果超出了这个范围,底层就会new新对象,==号比较就会false。
      3. 两个都是new出来的,即两个new Integer比较,则为false。
      4. int与Integer、new Integer()进行==比较时,结果永远为true,因为会把Integer自动拆箱为int,其实就是相当于两个int类型比较。
    5. Integer.parseInt()和Integer.valueOf()的区别:

      1. 都是传入一个String返回整形,如果无法转换都是抛出NumberFormatException
      2. Integer.parseInt()返回值是int
      3. Integer.valueOf()返回值是Integer
    6. double类型数值的计算经常会出现这种精度丢失的问题,尤其是有小数点的情况下,常常会因为精度丢失而导致程序出错。所以我们在运算高精度的数据的时候,可以使用 Java.math.BigDecimal 类(涉及到金钱都最好用此类,理论不能用double)


  1. &和&&、|和||的区别
    1. &、|:逻辑与和逻辑或
      1. 性能没这么高,不管左边对整个表达式成不成立,都会执行到右边的部分
      2. 主要进行位运算:对01进行运算
        1. &:有0 则整个表达式为0
        2. |:有1 则整个表达式为1
        3. ^:相同为0,相异为1
        4. ~:取反操作
    2. &&、||:短路与和短路或
      1. 性能更好,如果判断左边对整个表达式就可以确定了,就不会执行到右边的部分,用的更多

  1. 有关switch
    1. 用于做选择的,可以用if-else替代
    2. 和if-else的区别:
      1. switch的设计按道理来说,是比if-else要快的,但是在99.99%的情况下,他们性能差不多,除非case分支量巨大。
      2. switch只支持byte,int,short,char,enum,String的类型
      3. switch虽然支持byte,int,short,char,enum,String但是本质上都是int,其他的只是编译器帮你进行了语法糖优化而已。

  1. 如何跳出当前的多重嵌套循环

    1. 使用 break 带上标识(类似于goto,最好不用)

      //标识
      ok:
      for(int i=0;i<10;i++){for(int j=0;j<10;j++){if(j==5){//跳到循环外的ok出,即终止整个循环break ok;}}
      }
      
    2. 定义flag来进行判断

      boolean flag = false;
      for(int i=0;i<10;i++){for(int j=0;j<10;j++){if(j==5){//跳到循环外的ok出,即终止整个循环flag = true;break;}}if(flag) {break;}
      }
      

  1. 重写和重载:

    1. 重载:对于同一个类,如果这个类里面有两个或者多个重名的方法,但是方法的参数个数、类型、顺序至少有一个不一样,就构成方法重载(重载overload,方法重载就是对不同数据类型的数据实现相似的操作)

    2. 重写:当一个子类继承父类,而子类中的方法与父类中的方法的名称,参数个数、类型都完全一致时,就称子类中的这个方法重写了父类中的方法(重写@Override),或者实现类重写接口中的方法

      1. 子类重写父类的方法的时候,方法的访问权限必须public >= 父类方法的访问权限private
      2. 子类重写父类的方法的时候,返回值类型必须是父类方法的返回值类型 或 该返回值类型的子类(<=),不能返回比父类更大的数据类型
    3. 构造器是否可以被重写@Override

      构造器是不能被继承的,因为每个类的类名都不相同,而构造器名称与类名相同,所以根本谈不上继承。

      构造器不能被继承,所以就不能被重写。但是,在同一个类中,构造器是可以被重载的。


  1. ==和equals区别

    1. ==

      1. 对于基本数据类型,比较值
      2. 对于引用数据类型,比较地址
    2. equals()方法

      1. 是Object提供的一个方法

      2. Object中equals()方法的默认实现就是返回两个对象==的比较结果。但是equals()可以被重写,所以我们在具体使用的时候需要关注equals()方法有没有被重写。

        public boolean equals(Object obj) {return (this == obj);}
        

  1. 是否可以继承String类

    1. String 类是不能被继承的,因为他是被final关键字修饰的

      public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[];
      
    2. 从String的源码能看出来:

      1. 不能被继承
      2. 底层是char数组实现的
      3. 长度是不可变的,因为char数组是final修饰的

  1. String、StringBuilder、StringBuffer的区别
    1. String:不可变字符串(要操作少量的数据用 String)
    2. StringBuilder:可变字符串、效率高、线程不安全(单线程操作字符串缓冲区下操作大量数据 StringBuilder)
    3. StringBuffer:可变字符串、效率低、线程安全(多线程操作字符串缓冲区下操作大量数据 StringBuffer)(通过加synchronized锁来保证线程安全,类似的还有HashMap和HashTable)

  1. 静态变量和实例变量的区别
    1. 静态变量是有static修饰的,实例变量没有static修饰
    2. 静态变量是类变量,可以通过类名直接调用,也可以通过类的实例对象调用,它只有一份内存空间
    3. 实例变量是对象的变量,需要new出对象才能被调用,不同的对象的同一个实例变量 内存空间是不同的

  1. 你觉得 Java 好在哪儿?
    1. 跨平台(JVM):Java 是跨平台的,不同平台执行的机器码是不一样的,但是因为 Java 加了一层中间层 JVM ,所以可以做到一次编译到处运行(即Write once,Run anywhere)
    2. 垃圾回收机制(GC):Java 还提供垃圾自动回收功能,虽说手动管理内存意味着自由、精细化地掌控,但是很容易出错,在内存较充裕的当下,将内存的管理交给 GC 来做,减轻了程序员编程的负担,提升了开发效率,更加划算
    3. 语言生态好:Java 生态圈太全了,丰富的第三方类库、网上全面的资料、企业级框架、各种中间件等等,想要的都有。

  1. 抽象类和接口的区别:

    抽象类是对类的抽象(是一种模板设计方法)、而接口是对行为的抽象(是一种行为的规范)

    1. 一个类只能继承一个抽象类,但是可以实现多个接口
    2. 抽象类可以有构造方法,接口中不能有构造方法
    3. 抽象类中可以有抽象和具体方法,jdk8之前接口中只能有抽象方法,jdk8之后接口可以有默认方法和静态方法
    4. 不管是抽象类还是接口,抽象方法都不能由private修饰(因为抽象方法必须能被重写)

  1. 内部类和静态内部类:

    1. 都是写在类内部的类,区别在于加不加static修饰符
    2. 静态内部类,可以使用外部类名直接访问,外部类对应的不同对象访问的是同一个静态内部类对象。而对于非静态内部类,不能直接外部类名直接访问,且不同对象访问的是不同的内部类对象。
    3. 在内部类里面可以访问外部类的所有成员变量,静态内部类只能访问静态成员变量

    static关键字可以修饰什么?有什么语义?

    1. 修饰变量:表示静态变量、类变量,类加载的时候加载,内存中只有一份,类的多个对象共享同一个变量。
    2. 修饰方法:表示静态方法、类方法,可以通过对象调用,但是建议使用类名直接调用,静态方法只能访问静态的成员变量和方法,而且方法体中不能出现this关键字。
    3. 修饰类:表示静态类,而且就是静态内部类,静态内部类跟静态方法一样,只能访问静态的成员变量和方法

  1. 如何实现对象拷贝(复制):

    将要拷贝的对象对应的类:

    1. 实现Cloneable接口,并重写Object类中的clone()方法【具体实现待学习】
    2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆【具体实现待学习】
    3. 通过commons中的工具类BeanUtils或者PropertyUtils,调用对应的方法进行对象拷贝

    将一个对象a克隆给另一个对象b

    ​ 浅克隆:a和b指向的是同一个地址

    ​ 深克隆:a和b指向的是不同的地址,但是内容一样


  1. Java中有几种类型的io流

    1. 按照方向分:输入流和输出流
    2. 按照流中数据的最小单位:字节流(所有文件)和字符流(文本文件)
    img

    其他流:

    1. 缓冲流:提高读写数据的性能
    2. 转换流、打印流、数据流、序列化流

    简单介绍Java I/O,其中NIO、BIO、AIO三种I/O模式的区别【待学习】

    1. BIO是阻塞I/O,每个连接对应一个线程
    2. NIO是非阻塞I/O,多个连接共享少量线程
    3. AIO是异步I/O,允许应用程序异步地处理多个操作
    4. NIO、AIO通常比BIO更适用于高并发的网络应用,可以更有效地管理多个连接和I/O操作。
    5. AIO是适合高吞吐量的应用程序,可以异步处理多个I/O操作,而不需要线程等待。

  1. Java中Error和Exception的区别
    1. Error和Exception都是Java.lang.Throwable下的两个子类
    2. Error:系统级别的严重错误(开发人员不用管)
    3. Exception:
      1. RuntionException(运行时异常):编译的时候没有错误提醒,运行时出现的异常
      2. 其他异常(编译时异常):编译的时候出现错误就会提醒
      3. 还可以自定义异常,编译时异常继承Exception,运行时异常继承RuntionException
    4. 常见运行时异常:
      1. NullPointerException:空指针异常
      2. ArrayIndexOutOfBoundsException:数组下标越界
      3. ArithmeticException:算术异常
      4. ClassCastException:类型转换异常
    5. 常见编译时异常:
      1. ClassNotFoundException:类找不到异常
      2. FileNotFoundException:文件找不到异常
      3. IOException:IO 异常(输入输出异常)

  1. JDBC操作数据库的步骤:
    1. 加载(注册)数据库驱动(到JVM)。
    2. 建立(获取)数据库连接。
    3. 创建(获取)数据库操作对象。
    4. 定义操作的SQL语句。
    5. 执行数据库操作。
    6. 获取并操作结果集。
    7. 关闭对象,回收数据库资源(关闭结果集–>关闭数据库操作对象–>关闭连接)。
	String driver = "com.mysql.jdbc.Driver";// 数据库连接串String url = "jdbc:mysql://127.0.0.1:3306/jdbctest";// 用户名String username = "root";// 密码String password = "123456";// 1、加载驱动Class.forName(driver);// 2、获取数据库连接Connection conn = DriverManager.getConnection(url, username, password);// 3、获取数据库操作对象Statement stmt = conn.createStatement();// 4、定义操作的SQL语句String sql = "select * from user where id = 100";// 5、执行数据库操作ResultSet rs = stmt.executeQuery(sql);// 6、获取并操作结果集while (rs.next()) {}// 7、关闭对象,回收数据库资源rs.close();stmt.close();conn.close();

  1. Statement和PreparedStatement的区别

    Statement是Java执行数据库操作的一个重要方法,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句,PreparedStatement继承自Statement 。

    1. Statement:
      1. 对数据库只执行一次性存取的时侯,PreparedStatement 对象的开销比Statement大,所以可以用 Statement 对象进行处理。
      2. 不能防止sql注入,不安全
      3. 对应到MyBatis里面的${}
    2. PreparedStatement:#{}
      1. 是预编译的,对于批量处理可以大大提高效率。
      2. 能防止sql注入,安全
      3. 对应到MyBatis里面的#{}

  1. 数据库连接池的作用与基本原理

    正常的创建数据库连接的流程就是,先加载数据库驱动,再得到数据库连接,在数据库访问量比较大的情况下,频繁的创建连接会带来较大的性能开销,而数据库连接池就是维护了一定数量的数据库连接,当需要用的时候,就可以直接获取。

    好处:

    1. 资源复用,避免频繁创建数据库连接,造成资源的消耗
    2. 更快的系统响应速度,数据库连接池初始化后,就已经有了若干数据库连接对象,不需要重新创建
    3. 统一的连接管理,避免数据库连接泄露
    

    常见数据库连接池:

    1. Druid,最好的选择之一(高效的性能、高度的可扩展和可配置性、安全性)
    2. C3P0
    

    连接池的几个关键参数:

    1. 初始化连接数:表示启动的时候初始化多少个数据库连接
    2. 最大连接数:表示最多同时能使用多少个连接
    3. 最大等待时间:表示连接池里面的连接用完了以后,新的请求要获取连接 要等待的时间,超过这个时间就会提示超时异常
    

  1. 什么是DAO模式

    一种数据访问模式,DAO位于业务逻辑和持久化数据之间,实现对持久化数据的访问。通俗来讲,就是将数据库操作都封装起来,对外提供相应的接口。


  1. 获得一个类的类对象有哪些方式
    1. 方式一:调用运行时类的属性:类名.class
    2. 方式二:通过运行时类的对象,对象名.getClass()
    3. 方式三:调用Class的静态方法:Class.forName(String classPath)(类的全路径)

  1. JDK、JRE、JVM三者的关系

    在这里插入图片描述
    1. JDK:Java开发工具包,包括了Java运行环境jre、编译器、基础类库等等,是整个Java语言的核心。
    2. JRE:Java运行时环境,是运行基于Java语言编写的程序所不可缺少的运行环境,用于解释执行Java的字节码文件,其中包含了JVM。
    3. JVM:Java虚拟机,属于JRE,是整个Java实现跨平台的最核心的部分,负责解释执行字节码文件,是可运行Java字节码文件的虚拟计算机。

    对于开发者而言需要jdk,如果只是运行Java程序,配置jre环境即可


  1. JDK8有哪些新特性
    1. 引入了 Lambda 表达式(简化开发)
    2. 引入了 Stream 流(更加方便的操作集合和数组)
    3. 引入了新的日期API(更加方便的使用日期)
    4. 接口不再只有抽象方法,还可以有默认方法和静态方法

  1. Java中final、finally和finalize的区别
    1. final关键字用于声明某个变量(不可变)、方法(不可被重写)或者类(不可被继承)
    2. finally关键字用在异常处理中,无论try块中是否发生异常,finally块中的代码都会被执行(一般用于释放资源)。
    3. finalize不是关键字,而是Object类的一个方法,当垃圾回收器准备回收对象之前被调用,用于清理资源,可以在类中重写该方法(很少用到)。

  1. Java中的finally一定会被执行吗?

    不一定

    1. 如果程序都没有执行到try-catch-finally的代码块,比如try之前return了 或者 try之前就出现异常了(说明异常捕获的范围不够),那finally里面的内容肯定不会被执行。
    2. 即使程序已经执行到try里面了,如果出现了系统级别的错误,比如使用System.exit(0)把jvm关了,那finally也不会被执行了

  1. Java反射了解吗?

    实际开发中基本上没怎么用过,但是有学习过反射的原理。

    1. 反射其实就是Java提供的能在程序运行期间得到对象信息的能力,包括属性、方法、注解等,也可以调用其方法
    2. 一般的开发过程不会用到反射,在框架上用的较多,因为很多场景需要很灵活,所以不确定目标对象的类型,届时只能通过反射动态获取对象信息。

    反射的优点:

    1. 可以在程序运行期间 得到对象的信息,比如属性、方法,还能动态的调用方法
    2. 提高了代码的复用率,比如动态代理 就用到了反射来实现

    缺点:

    1. 使用反射以后,代码的可读性会下降
    2. 反射可以绕过一些限制访问的属性或者方法,可能会破坏了代码本身的抽象性

  1. Java泛型是什么?作用?

    在定义类、接口、方法时,同时声明了一个或者多个类型变量(如: ), 就称为泛型类、泛型接口,泛型方法,它们统称为泛型。

    1. 泛型的本质 就是把具体的数据类型作为参数 传给类型变量E,简单来说 就是在创建对象或者调用方法的时候才去明确具体的类型。
    2. 泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除
    3. 好处就是使得代码更加简洁(不再需要强制转换),程序更加健壮(在编译期间没有警告,运行期间不会出现类型转换异常)
    4. 泛型不支持基本数据类型,只支持对象类型(引用数据类型)。

  1. Java注解是什么?

    注解就是代码中的特殊标记,这些标记可以在编译、类加载、程序运行时被读取,然后就可以执行相应的处理。

    实际的开发中,比如在Spring、SpringMVC、SpringBoot这些框架中都有对应的注解,还比如会用到Lombok注解,记录日志之类的。除了在框架中的注解,Java原生也有一些常用的注解,比如说重写的注解@Overried等等。

    还有一种注解叫做元注解,它其实就是用来修饰注解的


  1. Java中throw和throws区别

throw是语句主动抛出一个异常,throw + 一个异常对象

throws是方法可能抛出的异常的声明,在声明方法的时候,throws + 该方法可能会抛出的异常的种类


  1. 为什么重写 equals() 就一定要重写hashCode() 方法?

我们自定义的类需要进行比较操作时,就需要重写 equals() 和 hashCode() 方法,如果不重写hashCode() 方法就会导致 该类无法使用基于散列的集合,比如HashMap、HashSet

比如说自定义了一个Student类,只重写了equals()方法,那么比较的就是两个对象的属性,如果两个对象的全部属性相同,equals()返回的就是true,但是此时两个对象的hashcode值是不一样的(采用的Object类默认的hashCode()方法),那这个时候,如果要把这两个对象放入map或者set中,因为hashcode值不一样,就会放到两个不同的位置,这样就违背了map和set的原理,所以也必须要重写hashCode()方法。

使得如果两个对象equlas()相等,那么hashcode也得相等,就不会在map或者set中放入重复的元素。


  1. String可以是:

    1. “ ”直接生成,会放在堆内存的字符串常量池中存储,且相同内容的字符串只会存储一份(为了节约内存)
    2. new出一个字符串对象,每次new都会产生一个新的对象放在堆内存中
  2. String对象都是不可变字符串对象


  1. 命令规范总结
    1. 包package:
      1. 小写英文字母
      2. 多层包之间用点进行分隔
      3. 采用域名倒写的方式,比如com.xx
    2. 类class、接口interface:
      1. 见名知意的名词
      2. 首字母大写
      3. 多个单词时,采用驼峰命名法
      4. 接口实现类加Impl、测试类加Test
    3. 方法method:
      1. 见名知意的名词
      2. 首字母小写
      3. 多个单词时,采用驼峰命名法
    4. 变量\属性:
      1. 见名知意的名词
      2. 首字母小写
      3. 多个单词时,采用驼峰命名法(对应的数据库表名用_)
    5. 常量:
      1. 见名知意的名词
      2. 全部大写字母
      3. 多个单词之间使用“_”进行分隔

  1. 常量是如何定义的?
    1. 使用 public static final 修饰符来声明一个静态常量,final表示不可以被修改,static表示是静态的(因为常量只需要一份),一般会用public修饰(因为常量要保证到处能够访问到)
    2. 当然也可以不加static,仅仅用final表示这个不可以被修改,也是常量(但是用的少)

  1. 变量\常量放在哪个内存空间?

    1. 类的成员变量(全局变量):
      1. 不论基本类型还是引用类型,变量名和值都随着类的实例(对象)存放在堆中
      2. 有初始值,基本数据类型为0,引用数据类型为null
    2. 方法的局部变量:
      1. 基本数据类型:局部变量变量名和值 都存放在虚拟机栈中
      2. 引用数据类型:局部变量变量名存放在栈中,而变量指向的对象存放在堆中
      3. 没有初始值,不给值会报错
    3. 常量(final修饰的):
      1. static final 修饰的常量,作为类信息,在类被加载的时候 放到了方法区
      2. 只加 final 修饰的常量,作为对象属性,在对象创建的时候被初始化 放到了堆空间
  2. 不能被重写的方法

    1. final修饰的方法
    2. 类的static静态方法,子类可以和父类定义名称相同的方法,但这并不是重写
    3. 抽象方法不能定义为private,因为不可见了,但它又必须被重写
    4. 构造器不能被重写
  3. 序列化和反序列化是什么?

    序列化是将对象转换为字节流,方便传输和存储,反序列化是将字节流转换为对象。

    实现一个Serializable接口,用于标记这个类可以被序列化,里面没有需要重写的方法,如果不实现这个接口的话,就会报异常。

    序列化ID的作用:

    它决定着能否成功的反序列化,在反序列化时,JVM会把字节流中的序列号ID 和 本地实体类中的序列号ID做比对,只有两者一致,才能反序列化,否则就会报序列化版本不一致的异常。

    一个类默认会有一个序列化ID,如果不变动这个类的话,那么反序列话的时候 序列化ID也会和这个实体类中的序列化ID保持一致,能够成功反序列化。但是如果后续对这个类进行字段的更改,序列化ID也会跟着改变,那再反序列化的时候,序列化ID就不一致了,就会报异常。所以最好不适用它默认的序列化ID,而是自己显式的声明一个确定的值。

    如果要使得某个字段不被序列化,可以使用transient关键字修饰(前提是通过实现Serializable接口来实现序列化)



Java集合

  1. 为什么数组索引从0开始呢?假如从1开始不行吗?

    因为在根据数组索引获取元素的时候,会用索引和寻址公式来计算内存所对应的元素数据,寻址公式是:数组的首地址+索引*存储数据的类型大小

    如果数组的索引从1开始,寻址公式中,就需要增加一次减法操作,对于CPU来说就多了一次指令,性能不高。


  1. ArrayList底层的实现原理是什么?

    1. ArrayList底层是用动态的数组实现的
    2. 初始容量:ArrayList初始容量为0,当第一次添加数据的时候才会初始化容量为10
    3. 扩容逻辑:ArrayList在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数组

  1. ArrayList list=new ArrayList(10)中的list扩容几次?

    1. 该语句只是声明和实例了一一个ArrayList,i指定了容量为10,未扩容
    2. ArrayList list=new ArrayList(11) 也是扩容0次,创建时直接分配其大小,没有扩充。

  1. 如何实现数组和List之间的转换?

    1. 数组转List,使用JDK中Java.util.Arrays. 工具类的asList方法

      List< String> list = Arrays.asList(strs);

      用Arrays.asList转List后,如果修改了数组内容,list受影响(指向同一块内存)

    2. List转数组,使用List的toArray方法。无参toArray方法返回Object数组,传入初始化长度的数组对象,返回该对象数组,

      String[] array = list.toArray(new String[list.size()]);

      List用toArray转数组后,如果修改了List内容,数组不受影响(不指向同一块内存)


  1. ArrayList和LinkedList的区别是什么?

    1. 都是List接口下的两个实现类
    2. ArrayList底层时动态数组实现的,LinkedList是双向链表实现的
      1. 双向链表相比单向链表每个结点多了个前驱指针,操作链表更灵活
    3. 效率方面:
      1. ArrayList查询的时候效率更高,插入和删除效率低
        1. 已知索引查询O(1),未知索引查询O(n)
        2. 插入删除O(n)
      2. LinkedList查询的时候效率低,插入和删除效率高
        1. 查询O(n)
        2. 插入删除O(n)(因为要遍历找到元素才能插入和删除)
    4. 内存空间占用:
      1. ArrayList底层是数组,内存连续,节省内存
      2. LinkedList是双向链表需要存储数据,和两个指针,更占用内存
    5. 两个变量都不是线程安全的,要保证线程安全:
      1. 在方法内使用,局部变量则是线程安全的
      2. 使用线程安全的ArrayList和LinkedList,new的时候Collections.synchronizedList(new ArrayList<>();

  1. 说一下HashMap的实现原理?

    1. 底层使用哈希表来实现,具体来说就是:key经过哈希函数得到hash值(调用hashcode()方法,再做个抑或运算),也就是得到数组下标,然后再放入元素。
    2. jdk1.8之前,哈希表是使用数组+链表来实现的,如果没有哈希冲突就直接把元素放在数组中,数组有冲突元素,就使用头插法的方式放到对应的链表中。有个缺陷就是链表长度可能过长,那么查找效率就会到O(n)
    3. jdk1.8之后,对哈希表进行了改进,使用数组+链表+红黑树来实现,与1.7的第一个不同就是出现了冲突元素,是采用尾插法的方式放到链表中,第二个改进就是当链表的长度大于8且数组长度大于64,就会将链表转换成红黑树,那就可以实现一个O(logn)的高效查找效率了。当然也不是一成不变就是红黑树,当元素个数等于6时,红黑树也会退化成链表。

  1. HashMap的put方法的具体流程 put(key,value)

    put()方法就是hashmap保存键值对的方法,传入一个key和value,然后具体的流程就是:

    1. 判断数组table是否为空或者为null,如果是的话,就需要执行resize()方法对hashmap进行初始化扩容。
    2. 根据key,通过哈希函数计算得到hash值,再取模数组长度得到数组索引i
    3. 判断数组对应的位置是否有元素,也就是table[i]是否为null
      1. 如果为null,说明当前位置没有元素,可以直接将value放进来
      2. 如果不是null,说明当前位置已经有元素了,又可以分为三种情况:
        1. 判断key是否一样,如果一样的话,就可以将value进行覆盖
        2. 如果key不一样,那就是发生了哈希冲突了,然后再判断当前索引下是不是红黑树:
          1. 如果是红黑树,就直接在树中插入新的键值对。
          2. 如果不是红黑树,说明就是链表,先遍历链表,看当前的key是否和链表中的key相同:
            1. 如果是,那就覆盖对应的value
            2. 如果不是,那就以尾插法的方式插入新的键值对,然后记住,要判断链表长度是否大于8 数组长度是否大于64,如果是的话,就要把链表再转换为红黑树。
    4. 将一个键值对put进入hashmap之后,会判断 目前存在的键值对数量size是否超过了扩容阈值(数组长度*装载因子0.75),如果超过了,就要进行扩容操作。

  1. HashMap的扩容机制

    1. 在第一次添加元素 或者 添加元素到了一定数量的时候,就需要扩容,实际就是调用resize()方法。
    2. 第一次扩容的时候,也第一次put元素的时候,数组长度为16,以后的扩容就是超过了扩容阈值(数组长度*默认的装载因子0.75)才开始的,比如说超过了12。
    3. 扩容的话,就扩容之前容量的两倍,比如说16 就是到 32。扩容之后会创建一个新的数组,就需要把原来的数据挪动到新数组中,分为三种情况
      1. 没有哈希冲突的节点,对key使用哈希函数得到哈希值,再得到新数组对应的索引位置
      2. 如果有哈希冲突:
        1. 如果是红黑树,走红黑树的添加逻辑
        2. 如果是链表,再走链表的添加逻辑,需要先遍历链表,拿到当前节点的哈希值 与运算(&)旧容量,看是否等于0
          1. 等于0的话,取出旧数组中的数据,直接赋给新数组,下标是一样的
          2. 不等于0的话,取出旧数组中的数据,赋给新数组,但是下标还要加一个旧容量的值

  1. HashMap的寻址算法【待学习】

  1. HashMap和HashTable有什么区别?

    1. HashMap不是线程安全的,HashTable是线程安全的

    2. HashTable的性能要差一些,因为加了synchronized锁来保证线程安全性。

      (通过加synchronized锁来保证线程安全,类似的还有StringBuilder和StringBuffer)

    3. HashTable底层的哈希表就是数组+链表,HashMap在jdk1.8之后变成了数组+链表+红黑树

    4. HashTable的初始化容量为11,HashMap的初始化容量为16


  1. LinkedHashMap和TreeMap底层数据结构是什么?

    1. LinkedHashMap是数组+链表+红黑树+双向链表(保证了插入有序)
    2. TreeMap是红黑树(保证了按照key排序)


多线程(并发编程)

多线程基础知识:

  1. 线程与进程的区别

    1. 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程(正在进行中的程序),一个进程之内可以包括一到多个线程。
    2. 一个线程就是一个指令流, 将指令流中的一条条指令以一定的顺序交给CPU执行。
    3. 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间。
    4. 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)
    5. 根本上的区别就是,进程是 操作系统资源分配 的基本单位,线程是 操作系统调度的 基本单位。

  1. 并行与并发的区别

    1. 单核CPU:

      由于只有一个CPU核心,所以所有线程都不能同时执行,但是CPU对于执行线程的切换速度很快,所以对外感觉多个线程是同时执行的,所以单核CPU没有并行,本质上还是并发。

    2. 多核CPU:

      CPU有多个核心,所以有的线程就可以同时被多个CPU核心执行,那么就达到了并行(真正的同时执行线程),但是一般线程数肯定大于CPU核心数,所以还是会有并发存在,所以多核CPU就是并行和并发同时执行。


  1. 线程创建的方式有那些

    1. (MyThread类)继承Thread类
      1. 重写run方法
      2. new一个线程对象MyThread
      3. 线程对象MyThread调用start方法启动线程
    2. (MyRunnable类)实现runnable接口
      1. 重写run方法
      2. 创建MyRunnable对象,并将对象封装到Thread对象中
      3. 线程对象Thread调用start方法启动线程
    3. (MyCallable类)实现Callable接口
      1. 重写call方法(有返回值)
      2. 创建MyCallable对象,并将对象封装到FutureTask对象中,再将FutureTask对象封装到Thread对象中
      3. 线程对象Thread调用start方法启动线程
      4. 通过FutureTask对象得到线程方法执行后的返回值
    4. 线程池创建线程,使用ThreadPoolExecutor类,设置七大参数(项目中一般都是用这个)

  1. runnable和callable有什么区别

    1. runnable无返回值、callable可以得到线程执行后的返回值(结合FutureTask对象)
    2. runnable实现的run()、callable实现的call()
    3. runnable实现的run()不能向上抛异常,只能在内部处理异常,callable实现的call()可以向上抛异常

  1. 线程的run()和start()有什么区别

    1. 线程对象调用start()才能启动一个新的线程,调用run()不能启动新的线程
    2. 同一个线程对象不可以多次调用start(),可以多次调用run()

  1. 线程包括哪些状态,状态之间是如何变化的

    六种状态:State枚举类里面有定义

    1. 新建态(NEW):新建态(创建线程对象)
    2. 可执行态(RUNNABLE):新建态调用start(),就变成了可执行状态(有执行资格,如果被执行了就是运行态,没有被执行就是就绪态)
    3. 阻塞态(BLOCKED):运行态(可执行状态)的线程没有获得锁就会变成阻塞态,拿到了锁就会变成就绪态(可执行状态)
    4. 等待态(WAITING):主动让运行态的线程调用wait(),就会变成等待状态,调用notify()唤醒线程,就会变成就绪态(可执行状态)
    5. 计时等待态(TIMED_WAITING):主动让运行态的线程调用sleep(),就会变成时间等待状态,等时间一过,就会变成就绪态(可执行状态)
    6. 终止态(TERMINATED):(线程终止)

  1. 新建三个线程,如何保证它们按顺序执行

    可以使用线程中的join方法解决


  1. 在Java中wait和sleep方法的不同

    1. wait() 、wait(long) 和sleep(long) 的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态
    2. 方法归属不同:
      1. sleep(long)是Thread类的静态方法
      2. wait() 、wait(long)都是Object的实例方法
    3. 唤醒时间不同:
      1. 执行了sleep(long)、wait(long)的线程都会在等待响应时间后主动被唤醒
      2. wait() 、wait(long)可以被notify()和notifyAll()唤醒,其中wait() 必须被notify()和notifyAll()唤醒
    4. 锁的性质不同(重点):
      1. wait方法的调用必须先获取对象锁,而sleep不用

  1. notify()和notifyAll()有什么区别

    1. notify():随机唤醒一个调用了wait()方法的线程
    2. notifyAll():唤醒所有调用了wait()方法的线程

  1. 如何停止一个正在运行的线程

    1. 使用stop方法强行终止(不推荐,方法已作废)
    2. 使用interrupt方法中断线程
      1. 打断阻塞的线程( sleep, wait, join)的线程,线程会抛出InterruptedException异常
      2. 打断正常的线程,可以根据打断状态来标记是否退出线程
    3. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止

  1. 什么是ThreadLocal?

    ThreadLocal是一种线程隔离机制,它提供了多线程环境下对于共享变量访问的安全性。

    在多线程中访问共享变量,要保证安全性,可以通过:

    1. 对共享变量加锁,从而保证同一时刻只有拿到锁的线程可以访问该变量,其他线程只能等待锁释放才能访问,但是加锁会带来一些性能开销,所以可以使用ThreadLocal来解决。
    2. ThreadLocal用了一种空间换时间的思想,在每个线程里面,都有一个容器来存储共享变量的副本,然后每个线程只能对这个变量的副本来进行操作,而不会影响到其他的线程对应的副本,那就可以保证了这个共享变量的安全性,又减少了加锁带来的开销。

    而且ThreadLocal提供了线程本地存储机制,利用该机制将数据缓存到某个线程内部,该线程可以在任意方法中获取缓存的数据。

    应用场景:

    瑞吉外卖项目,有个场景就是在某个方法中要获取当前登录用户的id来进行数据库的操作,但是不能直接拿到这个id,此时就想到可以使用ThreadLocal来解决,因为这个用户id可以在另一个方法中获取到(过滤器的doFilter方法中,如果session中有登录信息,就放行页面,并且将登录信息放到ThreadLocal中),而这两个方法同属于一个线程,一个线程内的变量,在不同的方法内可以共享访问,那就可以在那个方法中set一下这个变量,然后在当前方法中get一下这个变量即可。


  1. ThreadLocal的底层原理?

    在 Thread 类里面有一个成员变量ThreadLocalMap,它专门来存储当前线程的共享变量副本,后续这个线程对于共享变量的操作,都是从这个 ThreadLocalMap 里面进行变更,不会影响全局共享变量的值,key为threadLocal对象,value为具体的值,具体涉及的方法一般就是set()、get()、remove()。

    为了放置ThreadLocal出现内存泄漏的问题(key是弱引用的变量,value为强引用,key会被GC回收,value不会),可以使用remove()方法清理threadLocal对象。


并发安全:

  1. 你谈谈JMM (Java 内存模型)

    JMM:Java memory model Java内存模型(不是jvm中的内存结构)

    定义了共享内存中 多线程程序读写操作的行为规范,通过这些规则 来规范对内存的读写操作 从而确保指令的正确性

    JMM把内存分为两块:

    1. 工作内存:线程私有的工作区域
    2. 主内存:所有线程的共享区域
    3. 线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存

  1. 在Java中有什么锁?(Java的锁机制)

    按照锁的思想可以分为两类:

    1. 乐观锁:CAS

      乐观锁的思想就是线程即使没有拿到锁,也会继续去尝试

      1. 是一种比较并交换的思想,是一个原子性的操作。与之相对的,synchronized锁每次只会让一个线程去操作共享资源,而CAS相当于没有加锁,多个线程都可以访问共享资源,在实际修改的时候才会去判断能否修改成功。
      2. CAS的应用(配合volatile使用):
        1. 自旋锁:while(CAS),不断的进行CAS的操作,用于对于重量级锁的优化
    2. 悲观锁:

      悲观锁的思想就是线程如果没有拿到锁,就会被阻塞

      1. synchronized
      2. ReentryLock

  1. Java常见的加锁方式

    在并发编程中,加锁是一种常用的方法,用于保护临界区资源的访问安全

    1. synchronized关键字:修饰代码块或者方法,保证只有一个线程能够获取对象锁,其他线程阻塞,访问完毕后自动释放锁

      public synchronized void increment() {count++;
      }
      
    2. ReentryLock类:是一个可重入锁,相比于synchronized,ReentrantLock提供了更多的灵活性和可控性,例如可实现公平锁,可指定等待时间等。

      需要new一个ReentryLock对象,调用lock()方法主动开启锁,访问完毕后需要调用unlock()方法主动释放锁

      private Lock lock = new ReentrantLock();
      public void increment() {lock.lock();try {count++;} finally {lock.unlock();}
      }
      
    3. ReadWriteLock接口:是Java提供的一种读写锁机制,它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源,这种机制可以提高读操作的并发性能。

      需要new一个ReentrantReadWriteLock对象(ReadWriteLock接口的实现类),

      调用writeLock().lock()方法主动开启写锁,修改完毕后 需要调用writeLock().unlock()方法主动释放锁

      调用readLock().lock()方法主动开启读锁,读取完毕后 需要调用readLock().unlock()方法主动释放锁

      private ReadWriteLock lock = new ReentrantReadWriteLock();
      public void increment() {lock.writeLock().lock();try {count++;} finally {lock.writeLock().unlock();}
      }
      public int getCount() {lock.readLock().lock();try {return count;} finally {lock.readLock().unlock();}
      }
      
    4. 总结:

      1. synchronized关键字:适用于简单的加锁场景
      2. ReentrantLock类:具有更高的灵活性和可控性,例如公平锁、可中断锁、超时锁等
      3. ReadWriteLock接口:适用于读多写少的场景,提高读操作的并发性

  1. synchronized关键字的底层原理

    1. Synchronized关键字可以修饰代码块或者方法,它采用互斥的方式 让同一时刻只能有一个线程持有对象锁,其它线程再想获取这个对象锁时就会被阻塞住,所以就可使得代码块或者方法是线程安全的
    2. 不管synchronized修饰的是什么,对应的锁都是一个对象,在java中,一个对象其实由三部分组成:对象头、实际数据区、对齐填充区
      1. 对其填充区就是为了填满字节数用的,没什么说的
      2. 实际数据区就是对象的一些属性和状态
      3. 对象头中有个Mark Word会记录 这个对象关于锁的信息
    3. 每个对象都会有一个与之对应的Monitor对象(监视器,是由jvm提供,C+ +语言实现)
      1. 在monitor内部有三个属性,分别是owner、entrylist、 waitset
        1. owner:关联的是获得锁的线程,只能关联一个线程
        2. entrylist:关联的是处于阻塞状态的线程
        3. waitset:关联的是处于等待状态的线程
  2. synchronized在jdk1.8之前用的比较少,因为性能比较低,jdk1.8之后对锁进行了一些优化,具体有什么优化?

    1. synchronized锁的特点:锁的竞争不激烈就是轻量级锁,效率高,锁的竞争比较激烈就会转换称重量级锁,效率低
    2. 对锁的优化有:锁消除、锁膨胀、偏向锁等等

  1. 什么是AQS

    1. AQS叫做抽象队列同步器,它是一种锁机制,它是作为一个基础框架使用的,像ReentrantLock就是基于它实现的。
    2. 内部维护了一个双向队列,队列中就是排队等待获取锁的线程

  1. 什么是ReentrantLock

    1. ReentrantLock叫做可重入锁,一个线程可以多次调用lock方法
    2. 实现主要是通过CAS+AQS队列

  1. synchronized和Lock有什么区别

    1. 语法层面:
      1. synchronized是关键字,源码在jvm中,用C++语言实现
      2. Lock是接口,源码由jdk提供,用Java语言实现
      3. 使用synchronized时,退出同步代码块 锁会自动释放,而使用Lock时,需要手动调用unlock方法释放锁
    2. 性能层面:
      1. 在没有竞争时,synchronized性能较好,竞争激烈时,会从轻量级锁升级为重量级锁,性能较差
      2. 在竞争激烈时,Lock 的实现通常会提供更好的性能
    3. 功能层面:
      1. 二者均属于悲观锁

  1. 死锁产生的条件是什么

    死锁是指两个或两个以上的进程在执行过程中,由于竞争资源造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

    死锁需要必要的条件才能产生,死锁的四个必要条件:

    1. 互斥:一个资源每次只能被一个进程使用;
    2. 请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
    3. 不可抢夺:进程已获得的资源,在末使用完之前,不能强行抢夺;
    4. 循环等待:若干进程之间形成一种头尾相接的循环等待资源关系;

    解决死锁的方法:
    (1)死锁要尽可能的避免,也就是打破其中的必要条件(预防死锁)
    (2)真正的出现了死锁,就只能诊断到死锁的原因,然后采取一些措施,将死锁清除掉


  1. 在Java程序中出现了死锁,如何进行诊断

    1. 当程序出现了死锁现象,我们可以使用jdk自带的工具: jps和 jstack

      1. jps:输出JVM中运行的进程状态信息
      2. jstack:查看Java进程内线程的堆栈信息
    2. 还可以用其他可视化工具,例如jconsole(用于对jvm的内存、线程、类的监控,是一个基于jmx的GUI性能监控工具)


  1. 聊一下ConcurrentHashMap

    1. ConcurrentHashMap是线程安全的 而且是高效的map集合
    2. 底层实现:哈希表
      1. jdk1.8之前是采用分段的数组+链表,保证线程安全主要是通过 ReentrantLock可重入锁
      2. jdk1.8之后是采用数组+链表+红黑树,保证线程安全主要是通过 CAS的自旋锁+synchronized

  1. 请谈谈你对volatile的理解

    volatile关键字可以作用于变量(一般是共享变量,类的成员变量、静态成员变量),它使得变被修饰的变量具有两种语义:

    1. 保证变量对所有线程的可见性,也就是说当一个线程修改了volatile修饰的变量的值,这对于其他线程来说都能够立即知晓新值。

    2. 禁止进行指令重排序,正常来讲,如果对变量不使用volatile关键字修饰的话,编译器会对指令进行重排序优化来获得更好的性能,但是有时候这种编译器优化可能会导致程序没有产生预期的结果,所以如果需要阻止编译器对变量作重排序, 就可以使用volatile关键字来修饰。

  2. 导致并发程序出现问题的根本原因是什么

    并发问题出现,主要体现在三个方面:

    1. 原子性:指的是一个操作是不可以中断的,要么就全部执行,要么就都不执行。
    2. 可见性:是指当多个线程访问同一个变量的时候,一个线程修改了这个变量的值,那么其他线程能立马知晓这个变量的新值。
    3. 有序性:是指程序执行的顺序是按照代码的先后顺序来执行。

    如果没有满足其中的一点,就有可能出现并发问题。

    volatile关键字修饰的变量是可以保证可见性和有序性的,但是并不可以保证原子性,所以不能解决并发编程的问题。如果要满足并发编程的三个特性,可以使用synchronized或者lock锁来实现。


线程池:

线程池是一种池化技术,实现了资源的复用,具体好处有:

1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2. 提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3. 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

  1. 线程池的核心参数、线程池的执行原理(ThreadPoolExecutor类的七大参数)

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {
    
    1. corePoolSize:核心线程数
    2. maximumPoolSize:最大线程数(核心线程+临时线程)
    3. keepAliveTime:临时线程的生存时间
    4. unit:临时线程生存时间的 单位(s、ms)
    5. workQueue:阻塞队列(当没有空闲的核心线程时,新任务会放在阻塞队列中,阻塞队列一满,如果还有临时线程可用,就会分配给阻塞队列中的任务)
    6. threadFactory:线程工厂(可以定制线程对象的创建)
    7. handler:拒绝策略(当核心线程都在用+阻塞队列已满+临时线程都在用,再来任务就会拒绝)

    线程池的执行原理

    1. 提交任务交给线程执行
    2. 判断核心线程是否有空余
      1. 如果有,就把任务交给核心线程执行
      2. 如果没有,就将任务添加到阻塞队列
    3. 如果阻塞队列也已经满了,就判断是否有临时线程空余
      1. 如果有,就把任务交给临时线程执行
      2. 如果没有,就执行拒绝策略(抛出异常、直接丢弃任务)

  1. 线程池中有哪些常见的阻塞队列

    1. ArrayBlockingQueue: 基于数组结构的有界阻塞队列,FIFO

    2. LinkedBlockingQueue: 基于单向链表结构的有界阻塞队列,FIFO

    3. DelayedWorkQueue: 是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的

    4. SynchronousQueue: 不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

      主要使用的是数组和链表阻塞队列,具体的区别:

      1. 数组阻塞队列底层是数组实现的,链表阻塞队列底层是链表实现的
      2. 数组阻塞队列必须有界,也就是初始化的时候必须给容量,链表阻塞队列默认是无界的,当然也可以给个初始容量
      3. 数组阻塞队列会根据容量提前初始化好数组,链表阻塞队列添加数据的时候再创建节点
      4. 数组阻塞队列采用的是一把锁的机制,链表阻塞队列是头尾两把锁,效率更高,更多采用这个

  1. 线程池的拒绝策略有哪些?

    当核心线程数满了、阻塞队列满了、临时线程数满了,再来新任务就会引发拒绝策略

    1. AbortPolicy(默认的):抛出异常
    2. DiscardPolicy:直接丢弃任务
    3. CallerRunsPolicy:将任务返回给调用者,让调用者自行处理
    4. DiscardOldestPolicy:丢弃阻塞队列中最早的任务,然后就可以把新任务加进来

  1. 如何确定核心线程数

    1. lO密集型任务(比如文件读写、数据库读写):设置2N+1(N为CPU核数),大多数时候的业务场景

    2. CPU密集型任务(比如计算类型的代码):设置N+1

      //查看机器的CPU核数N
      Runtime.getRuntime().availableProcessors();
      

  1. 线程池的种类有哪些

    1. newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
    2. newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO)执行
    3. newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
    4. newScheduledThreadPool:可以执行延迟任务的线程池,支持定时及周期性任务执行

  1. 为什么不建议用Executors创建线程池

    涉及到一个Integer.MAX_INT,容易导致OOM内存溢出,一般还是采用ThreadPoolExecutor,设置七个参数来创建线程池。

【后续继续补充,敬请期待】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/227729.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pytorch05:卷积、池化、激活

目录 一、卷积1.1 卷积的概念1.2 卷积可视化1.3 卷积的维度1.4 nn.Conv2d1.4.1 无padding 无stride卷积1.4.2 无padding stride2卷积1.4.3 padding2的卷积1.4.4 空洞卷积1.4.5 分组卷积 1.5 卷积输出尺寸计算1.6 卷积的维度1.7 转置卷积1.7.1 为什么被称为转置卷积1.7.2 nn.Con…

Grafana增加仪表盘

1.Grafana介绍 grafana 是一款采用Go语言编写的开源应用&#xff0c;主要用于大规模指标数据的可视化展现&#xff0c;是网络架构和应用分析中最流行的时序数据展示工具&#xff0c;目前已经支持绝大部分常用的时序数据库。 Grafana下载地址&#xff1a;https://grafana.com/g…

Python+OpenGL绘制3D模型(六)材质文件载入和贴图映射

系列文章 一、逆向工程 Sketchup 逆向工程&#xff08;一&#xff09;破解.skp文件数据结构 Sketchup 逆向工程&#xff08;二&#xff09;分析三维模型数据结构 Sketchup 逆向工程&#xff08;三&#xff09;软件逆向工程从何处入手 Sketchup 逆向工程&#xff08;四&#xf…

Java强软弱虚引用

面试&#xff1a; 1.强引用&#xff0c;软引用&#xff0c;弱引用&#xff0c;虚引用分别是什么&#xff1f; 2.软引用和弱引用适用的场景&#xff1f; 3.你知道弱引用的话&#xff0c;能谈谈WeakHashMap吗&#xff1f; 目录 一、Java引用 1、强引用&#xff08;默认支持模式…

05-C++ 类和对象-继承

类与对象-03 继承与派生 1. 继承的概念 c最重要的特征是代码重用&#xff0c;通过继承机制可以利用已有的数据类型&#xff0c;来定义新的数据类型&#xff0c;新的类不仅拥有旧类的成员&#xff0c;还拥有新定义的成员。 一个 B 类继承于 A 类&#xff0c;或称从类 A 派生…

菜鸟网络Java实习一面面经

自我介绍&#xff0c;做过的项目 巴拉巴拉 你项目中用到redis&#xff0c;可以介绍一下为什么使用它吗&#xff1f; 基于内存操作&#xff0c;内存读写速度快。 支持多种数据类型&#xff0c;包括String、Hash、List、Set、ZSet等。 支持持久化。Redis支持RDB和AOF两种持久…

K8S异常处理

一、概述 1、k8s有时候会报错The connection to the server ip:6443 was refused - did you specify the right host or port &#xff0c;本文档提供几种可能产生该报错的原因和排障思路。 二、发现问题 使用任意Kubectl 命令会报错&#xff1a;The connection to the serv…

JVM GC 算法原理概述

对于JVM的垃圾收集&#xff08;GC&#xff09;&#xff0c;这是一个作为Java开发者必须了解的内容&#xff0c;那么&#xff0c;我们需要去了解哪些内容呢&#xff0c;其实&#xff0c;GC主要是解决下面的三个问题&#xff1a; 哪些内存需要回收&#xff1f; 什么时候回收&…

透过许战海矩阵洞察安记食品增长战略

引言&#xff1a;安记食品如果想实施增长战略&#xff0c;建议深耕招牌产品,走向全国市场,目前招牌产品咖哩和复合调味粉市场空间没有被全面释放出来,需要科学的产品战略作为支撑。安记食品选择功能性产品方向是正确的,但“功能性”需要一个大品类作为载体,牛奶,饮料是最大的载…

SpringBoot - Maven 打包合并一个胖 JAR 以及主项目 JAR 依赖 JAR 分离打包解决方案

问题描述 <plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.1.18.RELEASE</version><configuration><!--<classifier>exec</classifier>--…

产品经理学习-怎么写PRD文档

目录 瀑布流方法论介绍 产品需求文档&#xff08;PRD&#xff09;介绍 产品需求文档的基本要素 撰写产品需求文档 优先产品需求文档的特点 其他相关文档 瀑布流方法论介绍 瀑布流模型是一种项目的开发和管理的方法论&#xff0c;是敏捷的开发管理方式相对应的另一种方法…

Google机器人团队获ICRA 2023 机器人学习方向最佳论文奖:机器人实体控制的大语言模型程序

这篇论文主要讨论了大型语言模型&#xff08;LLM&#xff09;在机器人控制方面的应用。作者们指出&#xff0c;尽管LLM在理解和生成自然语言方面表现出色&#xff0c;但其在实际应用中&#xff0c;如机器人控制等领域的应用仍然有限。因此&#xff0c;他们提出了一种新的方法&a…

通过栈将中缀表达式转换为等价的后缀表达式

为什么要从中缀表达式转换为等价的后缀表达式&#xff1f; →使后缀表达式包含运算符优先级的信息&#xff0c;方便计算机能理解进行运算。 什么是中缀表达式&#xff1f; →中缀表达式虽然是人类看得懂的简单算术形式&#xff0c;但对计算机来说反而复杂难以理解 什么是后缀表…

【头歌实训】PySpark Streaming 入门

文章目录 第1关&#xff1a;SparkStreaming 基础 与 套接字流任务描述相关知识Spark Streaming 简介Python 与 Spark StreamingPython Spark Streaming APISpark Streaming 初体验&#xff08;套接字流&#xff09; 编程要求测试说明答案代码 第2关&#xff1a;文件流任务描述相…

<JavaEE> TCP 的通信机制(三) -- 滑动窗口

目录 TCP的通信机制的核心特性 四、滑动窗口 1&#xff09;什么是滑动窗口&#xff1f; 2&#xff09;滑动窗口的作用是什么&#xff1f; 3&#xff09;批量传输出现丢包如何处理&#xff1f; 1> 接收端ACK丢包 2> 发送端数据包丢包 4&#xff09;适用性 TCP的通…

Mysql 容易忘的 sql 指令总结

目录 一、操作数据库的基本指令 二、查询语句的指令 1、基本查询语句 2、模糊查询 3、分支查询 4、 分组查询 5、分组查询 6、基本查询总结&#xff1a; 7、子查询 8、连接查询 三、MySQL中的常用函数 1、时间函数 2、字符串函数 3、聚合函数 4、运算函数 四、表…

【elk-day01】es和kibana搭建及验证---Mac-Docker

Mac系统使用Docker下载搭建和验证eskibana Docker下载安装es安装es验证kibana安装kibana验证 Docker下载安装 Docker Desktop官网安装下载地址 说明一下为什么要安装desktop版本的docker&#xff0c;因为docker作为工具使用&#xff0c;我们需要的是开箱即用&#xff0c;没有必…

QT中的信号与槽的讲解

文章目录 信号及其特点槽及其特点代码演示标准信号与标准槽函数方式一方式二 自定义信号和槽connect()函数信号和槽函数存在函数重载的情况下Qt的信号槽机制注意事项 信号及其特点 信号&#xff1a;是一种特殊的函数&#xff0c;又称信号函数&#xff0c;俗称信号&#xff0c;…

2023年新一代开发者工具 Vue ,正式开源!

以下文章来源于前端充电宝 &#xff0c;作者CUGGZ 近日&#xff0c;Vue 新一代开发者工具&#xff08;DevTools&#xff09;正式开源&#xff01;Vue DevTools 是一个旨在增强 Vue 开发人员体验的工具&#xff0c;它提供了一些功能来帮助开发者更好地了解 Vue 应用。下面就来看…

第九部分 图论

目录 例 相关概念 握手定理 例1 图的度数列 例 无向图的连通性 无向图的连通度 例2 例3 有向图D如图所示&#xff0c;求 A, A2, A3, A4&#xff0c;并回答诸问题&#xff1a; 中间有几章这里没有写&#xff0c;感兴趣可以自己去学&#xff0c;组合数学跟高中差不多&#xff0c…