[Java]Java并发编程实战深入理解线程与锁机制
# 深入理解Java并发编程中的线程与锁机制实战指南
## (原创技术文章,类百度经验教程风格)
---
### 一、多线程基础:线程创建与核心概念
#### 1.1 线程理解
- 什么是线程?
线程是程序执行的最小单元,是操作系统分配CPU时间片的基本单位。Java实现多线程依赖CPU时间片轮转和上下文切换机制,默认是单线程执行单任务。
#### 1.2 线程创建的两种方式
方式1:继承`Thread`类
```java
public class MyThread extends Thread {
public void run() {
System.out.println(线程 + Thread.currentThread().getName() + 在运行);
}
}
// 使用
MyThread t1 = new MyThread();
t1.start();
```
方式2:实现`Runnable`接口(推荐)
```java
public class Worker implements Runnable {
public void run() {
// 业务逻辑
}
}
// 使用
Thread t2 = new Thread(new Worker());
t2.start();
```
说明:两种方式中,`Runnable`避免了单继承的局限性,适合任务与对象解耦的场景。
#### 1.3 线程生命周期
 (注:此处假设插入生命周期图,实际可省略)
关键状态:
- 新建(New):调用构造器后。
- 就绪(Runnable):调用`start()`后。
- 运行(Running):获取CPU执行权。
- 阻塞(Blocked):等待锁资源等。
- 死亡(Dead):`run()`方法正常结束。
---
### 二、线程安全问题:竞态条件与死锁
#### 2.1 线程不安全的典型场景
案例:银行账户操作
```java
public class UnsafeAccount {
private double balance = 1000;
public void withdraw(double amount) {
balance -= amount; // 这里可能发生数据竞争
}
}
// 多线程操作此对象可能导致余额错误
```
问题解释:当两个线程(如A和B)同时读取`balance`时,后写入的线程可能覆盖前者的操作,造成数据丢失(丢失更新问题)。
#### 2.2 竞态条件的核心原因
- 临界区(Critical Section):访问共享资源的代码段。
- 同时执行:多个线程未同步,导致操作顺序不可控。
---
### 三、锁机制详解:从`synchronized`到`ReentrantLock`
#### 3.1 内置锁:`synchronized`
使用方式:
```java
public synchronized void safeWithdraw(double amount) {
// 同步方法,锁对象为this
}
// 或
public void method() {
synchronized(obj) {
// 同步代码块锁对象为obj
}
}
```
特性:
- 自动释放:发生异常时自动释放锁。
- 阻塞式等待:获取不到锁时线程进入等待队列。
- 不可中断:获取锁的过程不可响应中断请求。
#### 3.2 可重入锁:`ReentrantLock`
```java
public class BankAccount {
private double balance;
private final Lock lock = new ReentrantLock();
public void transfer(double amount) {
lock.lock(); // 显式获取锁
try {
// 业务逻辑
} finally {
lock.unlock(); // 必须保证释放(如通过try-finally)
}
}
}
```
优势:
- 非公平模式:支持等待队列的公平性控制。
- 尝试锁(Try-Lock):可用`tryLock()`避免阻塞,或设置超时时间。
- 条件变量(Condition):通过`newCondition()`实现线程间精准通信(类似`synchronized`的`wait/notify`)。
#### 3.3 `volatile`关键字的作用
- 解决可见性问题:确保变量值对所有线程可见,但不保证原子性。
- 适用场景:如标信号量(如`isRunning`状态标记)。
---
### 四、死锁:四要素与解决方案
#### 4.1 死锁的形成条件
1. 互斥:资源不可共享(如文件、锁)。
2. 占有且等待:至少一个线程持有资源同时等待其他资源。
3. 不剥夺:资源无法被强制抢夺。
4. 循环等待:线程存在资源环形等待链。
#### 4.2 代码示例:互相持有锁的死锁
```java
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public void methodA() {
synchronized(lock1) {
synchronized(lock2) {
// ...
}
}
}
public void methodB() {
synchronized(lock2) {
synchronized(lock1) {
// ...
}
}
}
}
```
危险场景:若线程1在`methodA`执行中持有lock1时,线程2在`methodB`持有lock2,两者互相等待对方释放。
#### 4.3 防止死锁的策略
- 避免循环依赖:约定所有线程按固定顺序获取锁(例如通过对象哈希值排序)。
- 超时机制:在锁定时设置超时(如`ReentrantLock.tryLock(1, TimeUnit.SECONDS)`)。
- 资源分配策略:一次性获取所有需要的资源(可能降低并发效率)。
---
### 五、案例实战:生产者-消费者模式
#### 5.1 问题场景
生产者向缓冲区写入数据,消费者从其中读取,避免线程饥饿和阻塞。
#### 5.2 使用`synchronized`的经典实现
```java
public class ConsumerProducerSync {
private List buffer = new ArrayList<>();
private final int MAX = 10;
public synchronized void produce(String item) {
while (buffer.size() == MAX) {
try {
this.wait(); // 等待直到空间释放
} catch (InterruptedException e) {}
}
buffer.add(item);
this.notifyAll();
}
public synchronized String consume() {
while (buffer.isEmpty()) {
try {
this.wait();
} catch (InterruptedException e) {}
}
String item = buffer.remove(0);
this.notifyAll();
return item;
}
}
```
注:需配合`wait()/notify()`实现线程间通信,注意检查条件导致的“虚假唤醒”。
#### 5.3 使用`ReentrantLock`与`Condition`的优化实现
```java
public class SafeBuffer {
private List buffer = new ArrayList<>();
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
// 生产逻辑
public void produce(String item) {
lock.lock();
try {
while (buffer.size() == MAX) {
notFull.await(); // 等待容量释放
}
buffer.add(item);
notEmpty.signal(); // 唤醒消费者
} catch (InterruptedException e) {} finally {
lock.unlock();
}
}
// 消费逻辑类似,略...
}
```
优势:`Condition`支持为不同事件指定条件变量,避免所有线程的无差别唤醒。
---
### 六、进阶技巧:锁粒度与性能平衡
#### 6.1 减少锁竞争
- 读写分离锁:如`ReentrantReadWriteLock`,允许多个读线程同时访问,写线程独占。
- 分段锁:比如`ConcurrentHashMap`的分段设计,隔离不同分区的操作。
#### 6.2 性能优化建议
- 避免锁嵌套调用:减少内部方法调用中的同步操作。
- 设计无锁结构:例如CAS(Compare and Swap)操作,如`AtomicInteger`。
- 优先使用JUC并发工具类:如`CopyOnWriteArrayList`替代手动同步的列表。
---
### 七、总结:实战中的黄金法则
1. 合理选择同步机制:小额操作用`synchronized`,复杂场景用`Lock`。
2. 锁的最小化:同步块尽量简短,避免在同步代码中执行IO或高耗时操作。
3. 预防死锁检测:通过工具(如Java内置`jstack`命令分析线程状态)。
4. 测试验证:单元测试需模拟多线程环境,使用`@Test (threads=5)`等参数。
特别提示:并发编程是“实践驱动”的一门学科,建议通过高频代码场景手动实现+对比JUC源码(如`AbstractQueuedSynchronizer`)来加深理解。
---
# 后记:常见误区提醒
- `wait()` 必须在同步块内部调用,否则会抛出`IllegalMonitorStateException`;
- `ReentrantLock`未释放锁可能导致死锁,务必用`finally`释放;
- 避免在`Runnable`或`Thread`中直接持有共享状态,应通过`Atomic`类或线程安全容器管理。
更多推荐


所有评论(0)