在Java等编程语言的面试中,并发编程是一个非常重要的部分。它不仅考验了面试者对多线程、线程安全、同步机制等概念的理解,还要求面试者能够运用这些知识解决实际问题。本文将总结一些常见的并发编程面试题及其答案,帮助大家更好地准备面试。
1. 什么是线程和进程?
进程是程序的一次执行过程,是系统运行程序的基本单位。一个进程在其执行的过程中可以产生多个线程。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的独立运行的单位。线程与进程相似,但线程共享进程的堆和方法区资源,而每个线程有自己的程序计数器、虚拟机栈和本地方法栈。
2. 线程和进程的区别及优缺点?
- 区别:
- 进程是资源分配的基本单位,线程是CPU调度的基本单位。
- 进程间相互独立,而同一进程中的线程共享进程的资源和地址空间。
- 进程切换的开销远大于线程切换的开销。
- 优点:
- 线程执行开销小,能够快速响应用户请求。
- 线程间通信方便,可以共享全局变量和内存资源。
- 缺点:
- 线程过多会导致系统资源消耗过多,降低系统性能。
- 线程间共享资源可能引发数据不一致和竞态条件。
3. 什么是并发与并行?
- 并发:同一时间段内,多个任务都在执行(单位时间内不一定同时执行)。
- 并行:单位时间内,多个任务同时执行。
在单核CPU上,多线程主要通过时间片轮转实现并发;在多核CPU上,多线程可以实现真正的并行。
4. 线程安全是什么?
线程安全是指在多线程环境下,一个代码块或对象能够在同时被多个线程访问时正确地工作,而不会出现不一致或错误的结果。线程安全的代码应该能够处理并发访问,避免竞态条件和数据不一致。
5. 什么是竞态条件?
竞态条件是指在多线程环境下,由于线程之间的竞争和共享资源的访问,导致程序的执行结果依赖于线程的执行顺序或时间,从而可能产生不确定或错误的结果。
6. 线程池的概念和作用?
线程池是一种线程使用模式,它预先创建一组线程,并将任务提交给线程池来执行,而不是每次都创建新的线程。线程池的作用包括提高性能、节省资源、更好的线程管理和控制。
7. 常见的并发工具和技术有哪些?
常见的并发工具和技术包括线程、锁(如互斥锁、读写锁)、信号量、条件变量、原子操作等。Java中还提供了ExecutorService、ConcurrentHashMap等高级的并发框架和库。
8. 锁和监视器的概念?
- 锁:是一种同步机制,用于在多线程环境下保护共享资源的访问。
- 监视器:是一种与锁相关的概念,它提供了对共享资源的互斥访问,确保在任何时刻只有一个线程可以访问受保护的代码块或数据。
9. 常见的线程安全问题及解决方法?
- 数据竞争:多个线程在同时访问和修改共享数据时,出现未正确同步的情况,导致数据不一致。解决方法包括使用锁、同步块等同步机制。
- 死锁:两个或两个以上的线程相互等待对方释放资源,导致所有线程都无法继续执行。解决方法包括合理的资源分配和申请顺序、使用锁的层次结构等。
- 活锁:线程之间相互阻塞,但没有形成真正的锁,导致线程不断尝试但无法前进。解决方法包括引入超时机制、随机等待等。
10. Java中的守护线程和用户线程的区别?
Java中的线程分为守护线程(Daemon)和用户线程(User)。守护线程是为其他线程提供服务,如果所有的用户线程已经撤离,守护线程将随JVM一起结束。用户线程则是程序的主要执行线程,它们的结束才标志着程序的结束。通过Thread.setDaemon(bool on)
方法可以设置线程的守护状态,但必须在Thread.start()
之前调用。
11. Java Concurrency API中的Lock接口是什么?
Lock接口比同步方法和同步块提供了更具扩展性的锁操作。它支持更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。Lock接口的优势包括可以使锁更公平、可以使线程在等待锁的时候响应中断、可以让线程尝试获取锁并在无法获取锁的时候立即返回或等待一段时间等。
12. 线程安全的集合类和并发数据结构?
详情请见本人的另一篇博客(线程安全的集合类和并发数据结构)
13. ConcurrentHashMap
的工作原理是什么?
ConcurrentHashMap
是 Java 并发包中的一个重要类,用于替代 Hashtable。它提供了比 Hashtable 更高的并发级别。ConcurrentHashMap
采用了分段锁(在 Java 8 及以后版本中,这一实现有所变化,但基本概念相似)的机制,将整个哈希表分为多个段(Segment),每个段分别维护自己的锁,从而实现更高的并发性。在 Java 8 中,这一实现被重构为使用更细粒度的锁,即使用 Node 数组加 CAS(Compare-And-Swap)操作和同步器(如 synchronized
)来减少锁的竞争。
14. Java 中的 volatile
关键字有什么作用?
volatile
关键字是 Java 提供的一种轻量级的同步机制。它主要有两个作用:
- 保证可见性:当一个变量被声明为
volatile
时,它的修改会立即被其他线程看到,保证了多线程环境下变量的可见性。 - 禁止指令重排序:
volatile
关键字可以禁止 JVM 的指令重排序优化,从而确保程序按照预期的顺序执行。
然而,需要注意的是,volatile
并不能保证操作的原子性,因此在需要原子操作(如自增、自减等)时,仍需使用 Atomic
类或其他同步机制。
15. Java 中的 Atomic
类是如何实现原子性的?
Java 并发包 java.util.concurrent.atomic
中提供了一系列原子类,如 AtomicInteger
、AtomicLong
、AtomicReference
等。这些类通过使用底层的 CAS 操作来实现原子性。CAS(Compare-And-Swap)是一种用于实现多线程同步的原子指令,它包括三个操作数:内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。整个比较并替换的操作是原子的。
16. Java 中的 CountDownLatch
、CyclicBarrier
和 Semaphore
分别用于什么场景?
-
CountDownLatch
:用于让一个或多个线程等待其他线程完成一组操作。它有一个计数器,初始值设置为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器的值减至0时,所有因调用await()
方法而等待的线程都会被释放,继续执行。 -
CyclicBarrier
:允许多个线程在某个同步点上互相等待,直到所有线程都到达该点后再一起继续执行。与CountDownLatch
不同,CyclicBarrier
的计数器在达到某个值后会自动重置,因此它可以被重复使用。 -
Semaphore
:是一种信号量,用于控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。它可以被用来实现资源池、线程池等。
17. Java 并发包中的 Executors
工厂类提供了哪些线程池?
Executors
工厂类提供了多种线程池的创建方式,包括:
newSingleThreadExecutor()
:创建一个单线程的线程池,该线程池中的线程以顺序方式执行所有任务。newFixedThreadPool(int nThreads)
:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。newCachedThreadPool()
:创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。newScheduledThreadPool(int corePoolSize)
:创建一个支持定时及周期性执行任务的线程池。
18. 线程饥饿与线程死锁的区别?
- 线程饥饿:是指某个或者某些线程因为无法访问到它所需要的资源而无法执行。这种情况通常是因为更高优先级的线程持续地占用资源,导致低优先级的线程永远无法获得资源。
- 线程死锁:是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
以上就是关于Java并发编程的一些常见面试题及其解答。希望这些内容能帮助你更好地准备面试。