Java 并发面试问题全解析
简介
在 Java 开发领域,并发编程是一个至关重要且颇具挑战性的主题。理解 Java 并发相关的概念、掌握其使用方法以及熟悉常见和最佳实践,不仅有助于编写出高效、可靠的多线程程序,也是在面试中脱颖而出的关键。本文将围绕 Java 并发面试问题展开深入探讨,帮助读者全面掌握这一技术领域的要点。
目录
- 基础概念
- 线程与进程
- 并发与并行
- 线程安全
- 使用方法
- 创建线程的方式
- 线程的生命周期与状态转换
- 线程同步机制
- 常见实践
- 生产者 - 消费者模型
- 线程池的使用
- 最佳实践
- 避免死锁
- 性能优化
- 小结
- 参考资料
基础概念
线程与进程
进程是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间和系统资源。
线程是进程中的一个执行单元,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源。
并发与并行
并发是指在同一时间段内,多个任务都在执行,但不一定是同时执行。操作系统通过快速切换线程,让用户感觉多个任务在同时进行。
并行则是指在同一时刻,多个任务真正地同时执行,这需要多核 CPU 的支持。
线程安全
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的。
使用方法
创建线程的方式
- 继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running.");
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
- 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable is running.");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
- 实现 Callable 接口(有返回值)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get();
System.out.println(result);
}
}
线程的生命周期与状态转换
线程有以下几种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、等待(Waiting)、计时等待(Timed Waiting)和终止(Terminated)。状态转换图展示了线程在不同操作下如何在这些状态之间转换。
线程同步机制
- synchronized 关键字
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
- ReentrantLock
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
常见实践
生产者 - 消费者模型
import java.util.LinkedList;
import java.util.Queue;
class Producer implements Runnable {
private final Queue<Integer> queue;
private final int capacity;
public Producer(Queue<Integer> queue, int capacity) {
this.queue = queue;
this.capacity = capacity;
}
@Override
public void run() {
int value = 0;
while (true) {
synchronized (queue) {
while (queue.size() == capacity) {
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.add(value++);
queue.notify();
}
}
}
}
class Consumer implements Runnable {
private final Queue<Integer> queue;
public Consumer(Queue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int value = queue.poll();
System.out.println("Consumed: " + value);
queue.notify();
}
}
}
}
public class Main {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
int capacity = 5;
Thread producerThread = new Thread(new Producer(queue, capacity));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
}
}
线程池的使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Task implements Runnable {
private final int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task " + taskId + " is running.");
}
}
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
Task task = new Task(i);
executorService.submit(task);
}
executorService.shutdown();
}
}
最佳实践
避免死锁
- 确保线程获取锁的顺序一致。
- 尽量减少锁的持有时间。
- 使用定时锁(如 ReentrantLock 的 tryLock 方法),避免无限期等待。
性能优化
- 减少不必要的同步,只在共享资源访问处进行同步。
- 使用线程局部变量(ThreadLocal),减少线程间的竞争。
- 合理使用并发集合类(如 ConcurrentHashMap),提高并发性能。
小结
本文全面探讨了 Java 并发面试中常见的问题,从基础概念到使用方法,再到常见实践和最佳实践。通过理解和掌握这些内容,读者能够更好地应对面试挑战,并在实际开发中编写出高质量的并发程序。
参考资料
- 《Effective Java》
- 《Java Concurrency in Practice》
- Oracle Java 官方文档