Java基础知识面试题
1、说下你对==和equals的认识,它们有什么差别?(高频)
对于==号:
1、比较基本类型,比如int等,==比较的是值是否相同;
2、引用类型,比如自定义对象:比较地址是否相同;
2、String 类的常用方法都有那些?(高频)
indexOf():返回指定字符的索引。
length():返回字符串长度。
equals():字符串比较。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
3 String、StringBuilder、StringBuffer的区别?
String是不可变字符串,使用+号进行字符串拼接的时候会产生很多的临时碎片。
StringBuffer和StringBuilder是可变的字符串
StringBuilder是线程不安全的,效率高
StringBuffer是线程安全的,效率较低
4、一般的类和抽象类有哪些区别?
一般的类不能包含没有方法体的抽象方法,而抽象类可以包含抽象方法。抽象类不能直接用new来实例化,普通类可以直接实例化。
5、接口和抽象类有什么区别?(高频)
抽象类的子类要用 extends 来继承;而实现接口要用 implements 。
抽象类可以定义构造函数,而接口不能。
抽象类里可以定义 main 方法,但接口不能有 main 方法。
实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
5 Java的集合容器都有哪些?(高频)
6 List、Set、Map 之间的区别是什么?(高频)
List集合
1、属于单列集合,存储的都是一个一个元素
2、List和存在重复元素
3、每一个元素都有对应的索引,可以通过索引获取元素
4、元素的存取顺序一致
Set集合
1、属于单列集合,存储的都是一个一个元素
2、元素唯一
3、元素没有索引,不能通过索引获取元素
4、不能保证元素的存取顺序一致
Map集合:
1、属于双列集合,存储的是一对一对的元素
7、HashMap和Hashtable有什么区别?(高频)
相同点:
1、HashMap和Hashtable都实现了Map接口
2、都可以存储key-value数据
不同点:
1、HashMap可以把null作为key或value,Hashtable不可以
2、HashMap线程不安全,效率高。Hashtable线程安全,效率低。
8、ArrayList和LinkedList的区别?
ArrayList底层的数据结构是数组,查询快增删慢
LinkedList底层的数据结构是双向链表,查询慢增删快
9 jdk1.7到jdk1.8HashMap发生了什么变化(底层)?(高频)
1.8 之后 hashMap 的数据结构发生了变化,从之前的单纯的数组+链表结构变成数组 + 链表 + 红黑树。也就是说在 JVM 存储 hashMap 的 K-V 时仅仅
通过 key 来决定每一个 entry 的存储槽位(Node[]中的 index)。并且 Value 以链表的形式挂在到对应槽位上(1.8 以后如果 value长度大于 8 则转为红
黑树)。
10 HashMap的扩容原理?(高频)
初始化容量为16,达到阈值进行扩容。阈值 = 最大容量 * 负载因子(0.75),扩容每次2倍,总是2的n次方。
扩容机制:使用一个容量更大的数组替代已有的容量小的数组,transfer()方法将原有的Entry数组的元素拷贝到新的Entry数组里。
11 线程的创建方式?(高频)
① 继承Thread类
② 实现runnable接口
③ 实现Callable接口
④ 线程池创建线程
12 sleep() 和 wait() 有什么区别?(高频)
sleep是Thread类中的方法,不会释放同步锁
wait是Object类中的方法,会释放同步锁
13 线程的 run()和 start()有什么区别?(高频)
start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
run(): 封装了要被线程执行的代码,可以被调用多次。
14 有三个线程 T1 , T2 , T3 ,如何保证顺序执行?(高频)
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。
public class JoinTest {
public static void main(String[] args) {
// 创建线程对象
Thread t1 = new Thread(() -> {
System.out.println("t1");
}) ;
Thread t2 = new Thread(() -> {
try {
t1.join(); // 加入线程t1,只有t1线程执行完毕以后,再次执行该线程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2");
}) ;
Thread t3 = new Thread(() -> {
try {
t2.join(); // 加入线程t2,只有t2线程执行完毕以后,再次执行该线程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3");
}) ;
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
15 线程有哪些状态?
Java中的线程状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下:
public class Thread {
public enum State {
/* 新建 */
NEW ,
/* 可运行状态 */
RUNNABLE ,
/* 阻塞状态 */
BLOCKED ,
/* 无限等待状态 */
WAITING ,
/* 计时等待 */
TIMED_WAITING ,
/* 终止 */
TERMINATED;
}
// 获取当前线程的状态
public State getState() {
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
}
16 讲一下synchronized关键字的底层原理?(高频)
① synchronized同步代码块的情况
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中monitorenter 指令指向同步代码块的开始
位置,monitorexit 指令则指明同步代码块的结束位置。当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个
Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。当计数器为0则可以成功
获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要
阻塞等待,直到锁被另外一个线程释放为止。
② synchronized修饰方法的的情况
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是ACC_SYNCHRONIZED 标识,该标识指明了该方法是一
个同步方法,JVM 通过该 ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
17 Java中synchronized 和 ReentrantLock有什么不同?(高频)
相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下2项:
① 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。
② Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
(公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁。)
③ synchronized是通过jvm的指令实现同步,ReentrantLock是通过java api的实现同步的
18 ThreadLocal 是什么?有哪些使用场景?(高频)
ThreadLocal是一个线程工具类,可以在一个线程内共享数据,在我们的项目中使用ThreadLocal存储的是解析token以后的用户数据。
底层实现是同一个Map进行数据的存储,Map的键是当前线程对象,值就是要共享的数据。
19 如果你提交任务时,核心线程池已经满了,这时会发生什么?(高频)
① 无界队列
如果使用的是无界队列LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务
② 有界队列
如果使用的是有界队列比如ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根
maximumPoolSize的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy
20 创建线程池有哪几种方式?(高频)
① 通过Executor框架的工具类Executors来实现我们可以创建三种类型的:
-
FixedThreadPool: 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
-
SingleThreadExecutor: 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
-
CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线
程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
② 《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor 的方式:ThreadPoolExecutor最完整的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明
corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 时间单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null
任务的拒绝策略种类:
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
21 什么是Java内存模型?
JMM(Java Memory Model)Java内存模型,是java虚拟机规范中所定义的一种内存模型。
Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。
特点:
-
所有的共享变量都存储于主内存(计算机的RAM)这里所说的变量指的是实例变量和类变量。不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。
-
每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。
-
线程对变量的所有的操作(读,写)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存完成。