猿辅导Java面试真实经历与深度总结(二)
##
在面试中,掌握Java的基础知识和深入的理解是非常重要的。今天,我们来解析几个常见的Java面试问题,包括线程状态、线程池、深拷贝与浅拷贝、线程安全、Lock与Synchronized的区别,以及逃逸分析等话题。
---
### 1. 线程状态
Java中,线程有七种状态,它们是由 `Thread.State` 枚举类定义的。线程的状态随着程序的执行而发生变化,下面是七种状态的描述:
- **NEW**:线程被创建,但尚未启动。
- **RUNNABLE**:线程可以运行,或者已经正在运行。线程调度器选择合适的线程让它执行。
- **BLOCKED**:线程被阻塞,等待获得锁。
- **WAITING**:线程进入无限期等待,直到其他线程唤醒它。常见的场景包括 `Object.wait()`、`Thread.join()`、`LockSupport.park()`。
- **TIMED_WAITING**:线程在等待指定的时间后自动恢复,如 `Thread.sleep(1000)` 或 `Object.wait(1000)`。
- **TERMINATED**:线程执行完毕,或者因为异常等原因终止。
线程的状态转换过程是由JVM线程调度器来控制的,线程从一个状态到另一个状态的转换通常是由应用程序逻辑和操作系统调度来完成的。
---
### 2. 线程池
**线程池**(`ExecutorService`)是一种用于管理和复用线程的技术。它可以有效地避免频繁创建和销毁线程所带来的性能损失。线程池可以通过以下方式提高程序的性能和资源利用率:
- **核心池大小**:线程池中保持活动的线程数,默认情况下,`Executors.newFixedThreadPool()` 方法创建的是一个固定大小的线程池。
- **最大池大小**:线程池可以容纳的最大线程数。
- **任务队列**:线程池内部有一个队列,用于存储等待执行的任务。常见的队列有无界队列、有界队列等。
- **线程池的生命周期**:线程池有不同的状态,如`RUNNING`、`SHUTDOWN`、`STOP`等,表示线程池是否正在工作或是否已经关闭。
使用线程池的一个重要好处是可以管理系统中线程的最大数量,防止过多的线程导致系统资源过度消耗。
示例代码:
```java
import java.util.concurrent.*;
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(Thread.currentThread().getName() + " is working");
});
}
executor.shutdown();
}
}
```
---
### 3. 深拷贝,浅拷贝
- **浅拷贝 (Shallow Copy)**:复制对象的引用,而不是复制对象本身。如果原对象包含引用类型的字段,浅拷贝会复制这些字段的引用,而不是创建新的对象实例。这样,原对象和拷贝对象会共享同一个内部对象。
**浅拷贝示例**:
```java
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class ShallowCopy {
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = p1; // 浅拷贝
p2.name = "Bob"; // 修改p2的name
System.out.println(p1.name); // 输出"Bob"
}
}
```
- **深拷贝 (Deep Copy)**:完全复制对象以及对象中引用的所有对象。深拷贝会创建对象及其引用的对象的独立副本,因此原对象和拷贝对象之间没有任何共享部分。
**深拷贝示例**:
```java
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(Person other) {
this.name = other.name;
this.age = other.age;
}
}
public class DeepCopy {
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person(p1); // 深拷贝
p2.name = "Bob"; // 修改p2的name
System.out.println(p1.name); // 输出"Alice"
}
}
```
---
### 4. 线程安全
**线程安全**是指在多线程环境中,多个线程对同一个共享资源进行操作时,不会发生竞态条件(race conditions),也就是说,程序的行为是可预测的,且不会出现数据错误或异常。
- 常见的线程安全解决方案包括:
- **同步方法**:通过 `synchronized` 关键字对共享资源进行同步,保证同一时刻只有一个线程访问。
- **并发集合**:如 `ConcurrentHashMap`,它采用分段锁的机制提高并发度。
- **原子变量**:通过 `AtomicInteger` 等类提供原子操作,避免使用锁。
- **ReentrantLock**:通过显示锁来控制资源的并发访问。
---
### 5. Lock和Synchronized的区别
- **Synchronized**:
- 是Java的内置关键字,简单易用。
- 自动释放锁,不需要显式地调用 `unlock()`。
- 每个对象都有一个锁,保证同一时刻只有一个线程可以访问同步方法或代码块。
- 不支持公平性,可能会出现线程饥饿。
- **Lock**:
- 是`java.util.concurrent.locks`包中的接口,需要手动加锁和解锁。
- 支持更灵活的锁机制,比如公平锁、非公平锁。
- 可中断的锁,通过 `lockInterruptibly()` 可以响应中断。
- 可以在不同的作用域内持有锁,提供了更多的操作灵活性。
示例代码:
```java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock(); // 显式加锁
try {
// 线程安全的代码块
} finally {
lock.unlock(); // 释放锁
}
}
}
```
---
### 6. 逃逸分析
**逃逸分析**是Java虚拟机的一种优化技术,用于判断对象的引用是否会“逃逸”到方法外部。通过逃逸分析,JVM能够确定哪些对象可以在栈上分配,从而避免堆上的内存分配,并减少GC的负担。
- **栈上分配**:如果对象在方法内部没有逃逸,它就可以被分配到栈上,而不是堆上。这样可以减少垃圾回收的压力,因为栈上的对象在方法调用结束后会自动销毁。
- **同步消除**:如果通过逃逸分析确定某个对象在多线程环境中不会被共享,就可以消除不必要的同步操作,提升性能。
---
### 总结
以上就是关于线程状态、线程池、深拷贝和浅拷贝、线程安全、Lock与Synchronized的区别以及逃逸分析的简要解析。理解这些概念不仅能够帮助你应对Java面试中的常见问题,还能让更好地掌握并发编程和性能优化的技巧。