vlambda博客
学习文章列表

(小奇JAVA面试)每日10道Java面试题打卡—Java基础篇2

十一、HashMap和HashTable有什么区别?其底层实现是什么?

1、HashTable中每一个方法都加了锁,所以他是线程安全的,但是由于每个方法都加了锁,所以效率比较低,目前用的比较少。

2、HashMap允许Key和Value为null,而HashTable不允许。

3、底层实现:数组+链表

4、jdk8开始链表高度到8、数组长度超过64,链表转为红黑树。

5、如果产生hash冲突,先进行equals比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于64则将红黑树转为链表。

十二、ConcurrentHashMap原理,jdk7和jdk8版本的区别

ConcurrentHashMap和HashTable都是线程安全的,但是ConcurrentHashMap使用的是分段锁,所以效率比HashTable高。

1、jdk7:数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构。

2、锁:Segment分段锁,Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影响,并发度为Segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment

3、元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部。

4、get方法无需加锁,volatile保证

5、jdk8:数据结构:synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性,查找,替换,赋值操作都使用CAS

6、锁:锁链表的head节点,不影响其他元素的读写,锁力度更细,效率更高,扩容时,阻塞所有的读写操作、并发、扩容。

7、读操作无锁:Node的val和next使用volatile修饰,读写线程对该变量互相可见,数组用volatile修饰,保证扩容时被读线程感知。

十三、如何实现一个IOC容器

1、配置文件配置包扫描路径

2、递归包扫描获取.class文件

3、反射、确定需要交给IOC管理的类

4、对需要注入的类进行依赖注入

十四、什么是字节码?作用是什么

1、我们平时写完的代码是.java后缀的,我们通过编译成.class文件后就是字节码文件,字节码是java虚拟机可读的。

2、作用:java是将源码编译为.class文件,然后又通过虚拟机解释运行的,这样解决了传统解释性语言执行效率低的问题,同时又保证了解释型语言可移植的特点。

十五、Java类加载器有哪些

1、JDK自带有三个类加载器:bootstrap ClassLoader(引导类加载器)、ExtClassLoader(扩展类加载器)、AppClassLoader(应用类加载器)。

2、bootstrap ClassLoader是EctClassLoader的父类加载器,默认加载lib下的jar包。

3、ExtClassLoader是AppClassLoader的父类加载器,负责加载lib/ext文件夹下的jar包。

4、AppClassLoader是应用类加载器,负责加载classpath下的类文件。

十六、双亲委派模型

十七、Java中的异常有哪些

1、Java中的所有异常都来自顶级父类Throwable。

2、Throwable下有两个子类Exception和Error。

3、Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。

4、Exception不会导致程序停止,又分为两种,一个是RunTimeExcepion运行时异常,另一个是CheckedException检查异常。

5、RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败,CheckedException常常发生在程序编译过程中,会导致编译不通过。

十八、GC如何判断对象可以被回收

1、引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。

2、可达性分析法:从GC Roots开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象时不可用的,那么虚拟机就判断是可回收对象。

3、GC Roots的对象有:

1》虚拟机栈中引用的对象(比如New User(),那么这个User就是GC Roots)

2》方法区中类静态属性引用的对象

3》方法区中常量引用的对象

4》本地方法栈中引用的对象

十九、线程的生命周期,线程有哪些状态

1、线程通常有五种状态,创建、就绪、运行、阻塞和死亡状态。

2、阻塞的情况又分为三种:

1》等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中,进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类方法。

2》同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

3》其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态,当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法。

3、新建状态(New):新创建了一个线程对象。

4、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法,该状态的线程位于可运行线程池中,变得可运行,等待获取cpu的使用权。

5、运行状态(Running):就绪状态的线程获取了cpu,执行程序代码。

6、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃cpu使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态。

7、死亡状态(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期。

二十、sleep()、wait()、join()、yield()的区别

1、锁池

所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待CPU资源分配。

2、等待池

当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁,只用调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中。

3、sleep是Thread类的静态本地方法,wait则是Object类的本地方法。

4、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。

5、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。

6、sleep一般用于当前线程休眠,或者轮询暂停操作,wait则多用于多线程之间的通信。

7、sleep会让出cpu执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的。

8、yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取执行权继续执行。

9、join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程。