【Java并发编程面试指南】:面试官揭秘30个常见面试题及答案
发布时间: 2024-08-29 14:29:03 阅读量: 44 订阅数: 28
![Java并发算法优化技巧](https://img-blog.csdn.net/201804151133061?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FpX21pbmc4OA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
# 1. Java并发编程基础
在现代软件开发中,特别是在构建高性能的服务器端应用时,Java并发编程是一个不可或缺的技能。这一章将为读者搭建起并发编程的基础框架,为深入理解后续章节中更为复杂的并发模式和优化策略打下坚实的基石。
## 1.1 并发与并行的区别
在开始之前,理解并发和并行的区别至关重要。并发(Concurrency)指的是在单个处理器上通过多线程来同时处理多个任务的能力。尽管任务在逻辑上看似同时进行,但实际操作中,处理器在任一时刻只执行一个线程的指令,通过快速切换实现宏观上的“同时执行”。并行(Parallelism)则是指在多个处理器或多核处理器上同时执行多个任务,它能够真正地在同一时刻完成多项工作,提升了计算效率。
## 1.2 Java中的并发支持
Java提供了一套丰富的并发工具和API,支持开发者创建高效且安全的并发程序。从基本的Thread类和Runnable接口,到高级的并发集合如ConcurrentHashMap,再到并发控制类如ReentrantLock,Java的并发支持可以帮助开发者应对复杂的多线程编程挑战。
## 1.3 并发编程的目标
并发编程的主要目标是最大化CPU利用率和程序吞吐量,同时最小化资源竞争和同步开销。正确的并发程序设计可以显著提高程序的响应性和效率,但同时也引入了线程安全、死锁和性能优化等问题。了解并掌握这些基础知识,是设计高效并发程序的前提。
通过第一章的学习,读者将获得一个对Java并发编程概念和目标的全面了解,为后续章节关于多线程编程和并发工具的深入探讨做好准备。
# 2. Java多线程核心概念解读
## 2.1 线程的创建和运行
### 2.1.1 实现Runnable接口
在Java中,创建线程最常见的方式之一就是实现Runnable接口。Runnable接口包含了一个run方法,该方法包含了线程要执行的代码。通过实现Runnable接口,可以保持类的继承性,因为Java不支持多重继承,所以通过实现Runnable接口可以间接实现多线程。
```java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running");
}
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
```
在上述代码中,我们创建了一个实现了Runnable接口的类MyRunnable,并重写了run方法。然后在main方法中,我们将MyRunnable实例化后,创建一个Thread对象,并将这个Runnable实例传递给Thread的构造函数。调用Thread的start方法后,线程将启动并执行run方法中的代码。
### 2.1.2 继承Thread类
另一种创建线程的方式是直接继承Thread类。Thread类本身实现了Runnable接口,因此继承Thread类后,可以覆盖run方法来定义线程要执行的操作。
```java
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
```
在这个例子中,MyThread类继承自Thread,并且重写了run方法。同样地,通过在main方法中创建MyThread对象并调用start方法来启动线程。继承Thread类是一种更为直接的方法,但相对来说,它限制了类的继承体系,因为Java语言不支持多重继承。
### 2.1.3 线程池使用
在实际应用中,频繁地创建和销毁线程会消耗大量系统资源。为了解决这个问题,线程池技术应运而生。线程池可以复用线程,有效管理线程资源,减少系统开销。
```java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new MyRunnable());
executorService.execute(new MyRunnable());
// 关闭线程池
executorService.shutdown();
}
}
```
在这个示例中,我们使用了Executors工具类来创建一个固定大小的线程池。通过execute方法将Runnable任务提交给线程池执行。最后,调用shutdown方法关闭线程池,释放资源。使用线程池可以简化多线程编程,但需要注意合理配置线程池参数,避免资源浪费或死锁。
## 2.2 同步机制的理解与应用
### 2.2.1 synchronized关键字
synchronized关键字是Java中实现线程同步的重要机制。它可以保证在同一时刻,只有一个线程可以执行某个方法或代码块,从而避免多线程同时修改数据导致的数据不一致问题。
```java
public class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
public int getCount() {
return count;
}
}
```
在这个例子中,increment方法被synchronized关键字修饰。这确保了每次只有一个线程可以进入increment方法内部,修改count变量。使用synchronized关键字时需要注意,它会引入同步开销,因此要尽量减少同步块的范围,以提高效率。
### 2.2.2 Lock接口及其子类
除了synchronized关键字之外,Java还提供了Lock接口及其实现类,如ReentrantLock,以提供更灵活的锁操作。
```java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockCounter {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
```
在上面的代码示例中,我们使用了ReentrantLock来实现线程安全的计数器。ReentrantLock的使用提供了比synchronized关键字更高级的锁控制,例如锁的获取和释放可以出现在不同代码块中。try-finally结构确保了即使发生异常,锁也总是会被释放,这避免了死锁的发生。
### 2.2.3 Volatile关键字的使用
Volatile关键字用于声明变量,确保所有线程对这个变量的访问都是直接操作内存中的数据,而不是线程工作内存中的副本。
```java
public class VolatileDemo {
private volatile int count = 0;
public void increment() {
count++;
}
}
```
在该示例中,count变量被声明为volatile。这可以保证多线程环境下,对count的修改能立即对其他线程可见,从而保证计数的正确性。使用volatile关键字虽然可以解决一些线程安全问题,但它并不提供完整的线程同步机制,因此通常与synchronized或Lock联合使用。
## 2.3 线程间的通信与协作
### 2.3.1 wait()与notify()的使用
wait()和notify()是Object类的两个方法,它们提供了线程间的协作机制,允许线程等待某个条件成立,并在条件成立时得到通知。
```java
public class WaitNotifyDemo {
private static final Object lock = new Object();
private static int flag = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
while (flag != 1) {
System.out.println("t1 waits for flag to be 1");
lock.wait();
```
0
0