Java 并发面试问题全解析
简介
在 Java 开发的面试中,并发编程相关的问题是高频考点。Java 并发编程涉及到多线程、锁机制、并发工具类等多个方面,理解和掌握这些知识不仅能帮助开发者通过面试,更能提升实际项目中处理高并发场景的能力。本文将围绕 Java 并发面试常见问题,详细介绍基础概念、使用方法、常见实践以及最佳实践,帮助读者深入理解并高效运用 Java 并发编程知识。
目录
- 基础概念
- 进程与线程
- 并发与并行
- 线程安全
- 使用方法
- 创建线程的方式
- 线程同步机制
- 并发工具类的使用
- 常见实践
- 生产者 - 消费者模式
- 线程池的使用
- 最佳实践
- 避免死锁
- 合理使用并发工具类
- 小结
- 参考资料
基础概念
进程与线程
- 进程:是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间和系统资源。
- 线程:是进程中的一个执行单元,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源。
并发与并行
- 并发:指多个任务在同一时间段内交替执行。在单核 CPU 系统中,多个线程通过时间片轮转的方式实现并发执行。
- 并行:指多个任务在同一时刻同时执行。只有在多核 CPU 系统中才能实现真正的并行执行。
线程安全
当多个线程访问同一个共享资源时,如果不进行适当的同步控制,可能会导致数据不一致或其他异常情况。线程安全的代码能够保证在多线程环境下正常运行,不会出现数据竞争等问题。
使用方法
创建线程的方式
继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable is running");
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
实现 Callable 接口
import java.util.concurrent.*;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1 + 2;
}
}
public class CallableExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable callable = new MyCallable();
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(callable);
Integer result = future.get();
System.out.println("Result: " + result);
executor.shutdown();
}
}
线程同步机制
synchronized 关键字
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizedExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + counter.getCount());
}
}
Lock 接口
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class CounterWithLock {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
public class LockExample {
public static void main(String[] args) throws InterruptedException {
CounterWithLock counter = new CounterWithLock();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + counter.getCount());
}
}
并发工具类的使用
CountDownLatch
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("Task is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("All tasks are completed");
}
}
常见实践
生产者 - 消费者模式
import java.util.LinkedList;
import java.util.Queue;
class ProducerConsumerExample {
private static final Queue<Integer> queue = new LinkedList<>();
private static final int MAX_SIZE = 5;
static class Producer implements Runnable {
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == MAX_SIZE) {
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int item = (int) (Math.random() * 100);
queue.add(item);
System.out.println("Produced: " + item);
queue.notifyAll();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class Consumer implements Runnable {
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int item = queue.poll();
System.out.println("Consumed: " + item);
queue.notifyAll();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
Thread producerThread = new Thread(new Producer());
Thread consumerThread = new Thread(new Consumer());
producerThread.start();
consumerThread.start();
}
}
线程池的使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
System.out.println("Task is running on thread: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
最佳实践
避免死锁
- 保持锁的获取顺序一致,避免不同线程以不同的顺序获取锁。
- 使用定时锁,例如
tryLock
方法,避免线程无限期等待锁。
合理使用并发工具类
根据具体的业务场景选择合适的并发工具类,例如使用 CountDownLatch
来实现线程间的等待,使用 CyclicBarrier
来实现线程的同步等。
小结
本文围绕 Java 并发面试常见问题,详细介绍了基础概念、使用方法、常见实践以及最佳实践。通过学习这些知识,读者可以更好地理解 Java 并发编程,掌握多线程编程的技巧,提高处理高并发场景的能力。在实际开发中,需要根据具体的业务需求合理运用并发编程知识,避免出现线程安全等问题。
参考资料
- 《Effective Java》
- 《Java 并发编程实战》
- Oracle Java 官方文档