JavaSE面试问题汇总①
- int和Integer的区别
- 为什么设计封装类型?
- JDK、JRE、JVM的区别
- ==和equals方法的区别
- hashCode()与equals()之间的关系
- 泛型中extends和super的区别
- String、StringBuffer、StringBuilder的区别
- 重载和重写的区别
- 接口和抽象类的区别
- List与Set的区别
- ArrayList和LinkedList的区别
- ⭐HashMap与HashTable的区别?底层实现是什么?
- ⭐ConcurrentHashMap原理,jdk7和jdk8的区别
- java中的异常体系
- ⭐CopyOnWriteArrayList的底层原理
- ⭐Final的理解
- 线程的生命周期,线程有哪些状态
- 锁池与等待池
- ⭐Sleep()、wait()、join()、yield()的区别
- 对线程安全的理解
- Thread和Runnable的区别
- ⭐对守护线程的理解
- ⭐ThreadLocal的原理和使用场景
- ⭐⭐ThreadLocal内存泄漏原因,如何避免
- 并发、并行、串行
- ⭐并发三大特性
- ⭐为什么用线程池,解释下线程池参数
- ⭐简述线程池的处理流程
- ⭐线程池中阻塞队列的作用?为什么是先添加列队而不是先创建最大线程
- ⭐线程池中线程复用原理
总结了目前主流平台中常见的面试题,标⭐为重点!
int和Integer的区别
- 作为成员变量来说,Integer的初始值为null,int初始值为0
- Integer是对象,存储在堆内存;int类型直接存储在栈空间
- Integer封装了很多方法和属性,使用时更加灵活
为什么设计封装类型?
因为java本身是个面向对象的语言,一切操作都是以对象作为基础,比如集合(ArrayList,HashMap)里存的元素,也只支持存储Object类型,普通类型无法通过集合存储
JDK、JRE、JVM的区别
JDK是java标准开发包,提供编译、运行java程序所需的各种工具和资源,包括java编译器、java运行时环境,以及常用的java类库
JRE是java运行环境,用于运行java的字节码文件,JRE包括JVM和JVM工作时所需的类库,普通用户只需要安装JRE来运行java程序,而程序开发者必须安装JDK来编译调试程序
JVM是java虚拟机,是JRE的一部分,整个java实现跨平台的最核心部分,负责运行字节码文件
- 详细解释如下图:
==和equals方法的区别
==:如果是基本数据类型,比较的是值;引用类型比较的是引用地址
equals:具体看各个类重写equals方法后的比较逻辑,比如String类,虽然是引用类型。但是String类中重写了equals方法,内部比较的是各个字符是否全部相等
hashCode()与equals()之间的关系
首先hashCode的判断:
- 两个对象的hashCode不同,则肯定是不同的两个对象
- hashCode相同,对象不一定相同
- 两个对象相等。hashCode一定相同
所以重写 equals 方法的时候需要重写 hashCode 方法,许多容器类,如 HashMap、HashSet 都依赖于 hashcode 与 equals 的规定。
泛型中extends和super的区别
<? extends T> 表示包括T在内的任何T的子类
<? super T> 表示包括T在内的任何T的父类
String、StringBuffer、StringBuilder的区别
- String有final修饰,是不可变的,如果修改,会产生一个新的字符串,StringBuffer和StringBuilder是可变的
- StringBuffer线程安全,StringBuilder线程不安全(不同步)。所以单线程环境下StringBuilder效率更高
重载和重写的区别
重载:发生在同一个类中,方法名相同,参数类型、个数、顺序不同,返回值、访问修饰符不做要求。发生在编译时。
重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于父类,抛出异常范围小于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private子类就不能重写该方法。
public int add(int a,String b)
public String add(int a,String b)
//编译报错
接口和抽象类的区别
- 抽象类可以存在普通成员函数,而接口中只能存在public abstract方法
- 抽象类中的成员变量可以是各种类型,接口中成员变量只能是public final类型
- 抽象类只能继承一个,接口可以实现多个
扩展:
-
接口的设计目的是对类的行为进行约束,只约束行为的有无,不对如何实现进行限制。表达的是like a的关系,如Bird like a Aircraft但本质是is a Bird,核心是定义行为,实现类做法他不关心。
-
抽象类设计目的在于代码复用,如果不同的类具有相同的行为,我们就把共性的东西抽象出来,派生出抽象类。为is a的关系,如BWM is a Car。即先有子类再有父类,把子类共性抽取为父类。有些方法实现了,有些没实现,调的话不知道执行谁,所以抽象类不允许实例化出来
-
关心事物本质,用抽象类;关注操作,用接口。
List与Set的区别
- List:有序,按对象进入的顺序保存对象,可重复,孕妇多个Null元素对象,可以使用Iterator取出所有元素,再逐一遍历,还可以使用get(int index)获取指定下标的元素
- Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素,再逐一遍历各个元素
ArrayList和LinkedList的区别
- 底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于链表实现的
- 适应场景不同,ArrayList更适合随机查找,LinkedList更适合删除和添加
- 都实现了List接口,但是LinkedList还额外实现了Deque接口,所以LinkedList还可以当作队列来使用
ArrayList
LinkedList
⭐HashMap与HashTable的区别?底层实现是什么?
-
HashMap没有synchronized修饰,不安全。HashTable线程安全;
-
HashMap允许key和value为null,HashTable不允许
⭐ConcurrentHashMap原理,jdk7和jdk8的区别
java中的异常体系
解释:
Java中的所有异常都来自顶级父类Throwable。
Throwable下有两个子类Exception和Error。
Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。
Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。
RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。
CheckedException常常发生在程序编译过程中,会导致程序编译不通过。
⭐CopyOnWriteArrayList的底层原理
- 首先CopyOnWriteArraryList内部也是数组实现,再向CopyOnWriteArrayList添加元素时,会复制一个新的数组,写操作再新数组上进行,读操作在原数组上进行
- 写操作会加锁,防止出现并发写入丢失数据的问题
- 写操作结束后会把原数组指向新数组
- CopyOnWriteArrayList允许在写操作时来读取数据,提高了读性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList比较占内存,同时数据不是实时最新的数据,所以不适合实时性要求高的场景
⭐Final的理解
最终的
- 修饰类:不可被继承
- 修饰方法:不可被子类覆盖,但可以重载
- 修饰变量:表示变量一旦被赋值就不可被更改
进阶扩展:
**
线程的生命周期,线程有哪些状态
五种状态:创建、就绪、运行、阻塞、死亡
图片来源:https://www.pdai.tech/md/interview/x-interview.html
五状态描述
锁池与等待池
- 锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。 - 等待池
当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中
⭐Sleep()、wait()、join()、yield()的区别
对线程安全的理解
堆
栈
堆在虚拟机启动时创建,栈在线程开始时初始化
Thread和Runnable的区别
- Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,,然后执行run方法。
- 用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
网上常见的问题:下面两代码中Runnable和Thread运行结果为什么不同?
上述问题在于上面Thread new了俩对象,而Runnable new了一个对象,结果Runnable正常售出票。不能说明Runnable比Thread更安全!
⭐对守护线程的理解
- 守护线程不是某一个用户线程的,而是所有用户线程的守护线程。
- 守护线程依赖整个进程而运行,当没有线程要执行即进程结束了,守护线程就自动中断了。
- 守护线程的终止是自身无法控制的,不要把IO、File等重要的操作逻辑分配给它。
- thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
- 在Daemon线程中产生的新线程也是Daemon的。
- 守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。
- Java自带的多线程框架,比如Executorservice,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用ava的线程池。
⭐ThreadLocal的原理和使用场景
- 每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals它存储本线程中所有ThreadLocal对象及其对应的值
- ThreadLocalMap由一个个Entry对象构成
- Entry继承自weakReference<ThreadLocal<?>>一个Entry由ThreadLocal对象和object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收
- 当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
- get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
- 由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
使用场景:
- 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
- 线程间数据隔离
- 进行事务操作,用于存储线程事务信息。
- 数据库连接,Session会话管理。
Spring框架在事务开始时会给当前线程绑定一个Jdbc connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离。
⭐⭐ThreadLocal内存泄漏原因,如何避免
- 内存泄漏是什么?
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
并发、并行、串行
- 并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。
- 并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行
- 串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着
⭐并发三大特性
原子性、有序性、可见性
⭐为什么用线程池,解释下线程池参数
- 降低资源消耗:提高线程利用率,降低创建和销毁线程的消耗。
- 提高响应速度:任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
- 提高线程的可管理性:线程是稀缺资源,使用线程池可以统一分配调优监控。
⭐简述线程池的处理流程
⭐线程池中阻塞队列的作用?为什么是先添加列队而不是先创建最大线程
阻塞队列:
- 保存任务在阻塞队列中
- 在没有任务要执行的情况下,自动阻塞获取任务的线程(比如核心线程),释放cpu资源
⭐线程池中线程复用原理
业务逻辑并没有写在线程池中线程的run方法里,而是利用线程调用任务里面的run方法