Java经典面试题
一:HashMap,Hashtable,ConcurrentHashMap的区别:
HashMap、Hashtable和ConcurrentHashMap是Java中用于存储键值对的三种主要实现,它们在设计理念、线程安全性、性能以及使用方式上有所区别。
1. 设计理念与实现
HashMap:基于哈希表实现的Map接口,它使用数组+链表(JDK 1.7及以前)或数组+链表+红黑树(JDK 1.8)的数据结构。HashMap允许使用null键和null值,但不是线程安全的。HashMap的性能通常优于Hashtable,因为它没有同步的开销。
Hashtable:也是基于哈希表实现的,但所有方法都是同步的,即线程安全的。Hashtable不允许使用null键和null值。Hashtable的性能通常低于HashMap,因为它包含了同步的开销。
ConcurrentHashMap:设计用于多线程环境,它支持完全的并发访问,通过分段锁或其他同步机制来实现线程安全。ConcurrentHashMap的性能通常优于Hashtable,因为它使用了更细粒度的锁来减少线程竞争。
2. 线程安全性
HashMap:不是线程安全的,如果在多线程环境下使用,可能会导致数据不一致。可以通过Collections.synchronizedMap()方法来使HashMap线程安全,但这会降低性能。
Hashtable:是线程安全的,因为它的大部分方法都是同步的,这保证了在多线程环境下的数据一致性,但可能会影响性能。
ConcurrentHashMap:设计用于多线程环境,通过内部的同步机制来保证线程安全,同时提供了较高的并发性能。
3. 性能
HashMap:通常性能最好,因为它没有同步的开销,且允许使用null键和null值。
Hashtable:性能较差,因为它包含了同步的开销,且不允许使用null键和null值。
ConcurrentHashMap:提供了高性能的并发访问,通过内部的同步机制来减少锁竞争,适用于多线程环境。
4. 使用方式
HashMap:适用于单线程环境或需要外部同步的多线程环境。
Hashtable:适用于需要线程安全保证的场景,尽管其性能较低。
ConcurrentHashMap:适用于需要高性能并发访问的场景,特别是在多线程环境中。
综上所述,选择哪种Map实现取决于具体的应用场景和需求。如果应用主要在单线程环境中运行,且不需要考虑并发访问,那么HashMap可能是最佳选择。如果需要线程安全保证,但性能是一个关键因素,那么ConcurrentHashMap可能更适合。而如果应用必须在多线程环境中运行,且对数据的一致性有严格要求,那么Hashtable可能是一个选择,尽管它的性能通常较低
二:JVM内存模型和常用的GC回收算法
JVM内存模型主要分为五个部分:
-
程序计数器(PC Register)
-
虚拟机栈(VM Stack)
-
本地方法栈(Native Method Stack)
-
堆(Heap)
-
方法区(Method Area)
常见的GC(垃圾收集器)算法包括:
-
标记-清除算法(Mark-Sweep)
-
算法(Copying)
-
标记-压缩算法(Mark-Compact)
-
分代收集算法(Generational Collection)
Java中常见的垃圾收集器包括:
-
Serial收集器(新生代单线程收集器)
-
ParNew收集器(新生代并行收集器)
-
Parallel Scavenge收集器(新生代并行回收收集器)
-
Serial Old收集器(老年代单线程收集器)
-
Parallel Old收集器(老年代并行收集器)
-
CMS(Concurrent Mark Sweep)收集器(老年代并发收集器)
-
G1收集器(全新设计的收集器,可以同时使用新生代和老年代)
三:Java创建线程有哪些实现方式?其线程的声明周期是什么?
Java创建线程的实现方式主要包括以下几种:
- 继承Thread类:通过继承Thread类并重写其run方法,可以创建一个新的线程类。这种方式简单直接,但缺点是线程类继承了Thread类后,就不能再继承其他类了。
- 实现Runnable接口:通过实现Runnable接口并重写其run方法,可以创建一个线程任务。这种方式更为灵活,因为实现了Runnable接口的类还可以继承其他类。
- 实现Callable接口:Callable接口允许线程返回执行结果,通常与FutureTask一起使用,用于获取线程的执行结果。这种方式适用于需要返回执行结果的多线程任务。
- 通过线程池创建线程:使用ExecutorService和Executors工具类提供的静态方法创建线程池,然后提交Runnable或Callable任务到线程池中执行。这种方式适用于需要管理多个线程的情况,可以提高性能和资源利用率。
线程的生命周期主要包括以下几个阶段:
- 新建状态(New):新创建了一个线程对象。
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法,该状态的线程位于可运行线程池中,等待获取CPU的使用权。
- 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
- 阻塞状态(Blocked):线程因为某种原因放弃CPU使用权,暂时停止运行。阻塞的情况包括等待阻塞、同步阻塞和其他阻塞。
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
了解这些实现方式和生命周期有助于更好地管理和控制多线程应用中的线程行为
本文来自博客园,作者:LiJialong,转载请注明原文链接:https://www.cnblogs.com/carver/p/18396220