Java 面试题解析:CountdownLatch、线程池、CAS、synchronized、Lock、多态等关键概念
在 Java 面试中,除了基础的语法知识外,面试官还会考察一些更深入的多线程编程和并发控制等高级话题。本文将详细解答一些常见的 Java 面试题,包括 CountDownLatch
、线程池的 submit
和 execute
方法 、Java 多态的实现原理 、CAS 、synchronized
和 Lock
等技术概念,并提供代码示例加以解释。编辑
1. CountDownLatch
CountDownLatch
是一个并发工具类,它允许一个或多个线程等待直到其他线程的操作完成。它的核心概念是通过一个计数器来控制线程的等待和释放。线程在 CountDownLatch
中调用 await()
方法等待,直到计数器达到零时才会被释放。计数器通过调用 countDown()
方法递减。编辑
使用场景:
- 等待多个线程完成某项任务后,主线程才能继续执行。
- 例如,在测试时等待多个线程完成工作再进行下一步操作。
示例:
java
import java.util.concurrent.CountDownLatch;
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
Runnable task = () -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " done!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 计数器减1
}
};
for (int i = 0; i < threadCount; i++) {
new Thread(task).start();
}
latch.await(); // 主线程等待,直到计数器为0
System.out.println("All threads are done!");
}
}
输出:
bash
Thread-0 done!
Thread-1 done!
Thread-2 done!
All threads are done!
2. submit/execute
编辑
在 Java 中,submit()
和 execute()
都是线程池(ExecutorService
)的常用方法,用于提交任务给线程池执行。它们之间的主要区别在于返回值和任务处理方式。
execute(Runnable)
:用于提交一个Runnable
类型的任务,不会返回任务的执行结果。任务执行时,如果发生异常,异常会被吞掉。编辑
submit(Runnable)
:用于提交一个Runnable
类型的任务,会返回一个Future
对象,可以通过Future
对象获取任务的执行结果或异常。
区别总结:
execute
没有返回值,适合处理那些不关心任务执行结果的场景。submit
返回Future
对象,可以用于获取任务的执行状态、结果以及异常。
示例:
java
import java.util.concurrent.*;
public class TestSubmitExecute {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable task1 = () -> System.out.println("Task1 executed");
Callable<String> task2 = () -> {
Thread.sleep(1000);
return "Task2 result";
};
executor.execute(task1); // 不返回结果
Future<String> future = executor.submit(task2); // 返回 Future
System.out.println(future.get()); // 输出 "Task2 result"
executor.shutdown();
}
}
3. Java 多态实现原理
Java 中的多态(Polymorphism)是指同一个方法调用,可以根据对象的不同而表现出不同的行为。Java 实现多态的原理主要通过 方法重写 和 方法动态绑定 来实现。
- 方法重写:子类重新定义父类的方法。
- 方法动态绑定:在运行时,根据对象的实际类型来决定调用哪个方法。
多态的实现原理:
- 方法调用时的动态绑定:Java 使用虚拟机(JVM)中的方法调用机制,方法的调用由编译时决定调用哪一个类的版本,但是实际方法的调用是在运行时动态绑定的。
示例:
java
class Animal {
public void sound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
Animal animal = new Dog(); // 多态:父类引用指向子类对象
animal.sound(); // 输出 "Dog barks"
}
}
总结:Java 通过继承和方法重写实现多态,方法调用由 JVM 在运行时根据对象类型决定,称为动态绑定。
4. 线程池
线程池是为了避免频繁创建和销毁线程的开销,它通过复用线程来提高性能。Java 提供了 Executor
框架来管理线程池,常用的线程池有:
- FixedThreadPool:线程池大小固定。
- CachedThreadPool:线程池大小可以根据需要调整。
- SingleThreadExecutor:单线程执行任务。
- ScheduledThreadPool:可以定时执行任务。
线程池的常见方法有 submit
和 execute
。
示例:
java
import java.util.concurrent.*;
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
Runnable task = () -> System.out.println(Thread.currentThread().getName() + " is executing task");
for (int i = 0; i < 5; i++) {
executorService.submit(task); // 提交任务
}
executorService.shutdown(); // 关闭线程池
}
}
总结:线程池通过复用线程来提高性能,避免了频繁创建销毁线程的开销。
5. CAS(Compare-And-Swap)
CAS(比较并交换)是一种无锁的并发控制技术,通常用来实现原子操作。它通过原子地比较某个内存位置的值和预期值,如果两者相等,则将该内存位置的值更新为新值。
CAS 的优点是避免了加锁机制带来的性能问题,但也存在"ABA 问题",即值从 A
变为 B
再变回 A
,CAS 无法检测这种变化。
示例:
java
import java.util.concurrent.atomic.AtomicInteger;
public class TestCAS {
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) {
// 使用 CAS 自增
System.out.println("Counter: " + counter.getAndIncrement()); // 输出 0
System.out.println("Counter: " + counter.get()); // 输出 1
}
}
总结:CAS 是一种高效的并发控制技术,能够在不加锁的情况下实现原子操作,但需要注意一些细节问题,如 ABA 问题。
6. synchronized
synchronized
是 Java 提供的一种同步机制,用于解决多线程并发访问共享资源时的线程安全问题。它可以修饰方法或代码块,保证同一时刻只有一个线程能够执行被修饰的代码。
原理:
- 对象锁:每个对象都有一个锁,可以通过
synchronized
修饰方法来获取锁。 - 类锁:如果
synchronized
修饰的是静态方法,则是类级别的锁。
示例:
java
public class TestSynchronized {
private int counter = 0;
public synchronized void increment() {
counter++;
}
public synchronized int getCounter() {
return counter;
}
public static void main(String[] args) throws InterruptedException {
TestSynchronized test = new TestSynchronized();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
test.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Counter: " + test.getCounter()); // 输出 2000
}
}
总结 :synchronized
是 Java 内置的同步机制,通过锁来保证线程安全。
7. Lock
Lock
是 Java 并发包中的接口,它提供了比 synchronized
更灵活的锁机制。Lock
可以更精确地控制锁的获取和释放,支持公平锁、重入锁等特性。
常用的实现类是 ReentrantLock
。
示例:
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
private int counter = 0;
private Lock lock = new