Java多线程中的并发控制与同步机制
1. 并发问题的根源
在多线程编程中,当多个线程同时访问和修改共享数据时,可能产生不可预期的结果。以下代码展示了典型的竞态条件:
public class CounterExample {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter++;
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter++;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终计数: " + counter);
}
}
尽管期望结果是20000,但实际输出往往小于该值。这是因为counter++操作包含三个步骤:从内存读取值、执行加法、写回内存。这些步骤不具备原子性,在线程切换时可能导致更新丢失。
2. 使用 synchronized 实现同步
为确保操作的原子性,Java提供了synchronized关键字来实现互斥访问。
2.1 同步代码块
通过指定一个锁对象,保证同一时刻只有一个线程能进入临界区:
public class SyncBlockExample {
private static int counter = 0;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (lock) {
counter++;
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (lock) {
counter++;
}
}
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("同步后结果: " + counter);
}
}
2.2 同步方法
将synchronized修饰符应用于实例方法或静态方法:
class Counter {
private int value = 0;
// 实例方法锁住当前对象(this)
public synchronized void increment() {
value++;
}
// 静态方法锁住类对象
public static synchronized void staticIncrement() {
// 操作静态变量
}
}
public class SyncMethodDemo {
public static void main(String[] args) throws InterruptedException {
Counter c = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) c.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) c.increment();
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("方法同步结果: " + c.value);
}
}
3. synchronized 的关键特性
- 互斥性:同一时间仅允许一个线程持有锁。
- 可重入性:同一线程可多次获取同一把锁,避免自我阻塞。JVM维护锁的持有计数,每次加锁+1,解锁-1,直到归零才真正释放。
public class ReentrantTest {
private static final Object reentrantLock = new Object();
public static void main(String[] args) {
Thread t = new Thread(() -> {
synchronized (reentrantLock) {
System.out.println("外层");
synchronized (reentrantLock) {
System.out.println("内层");
}
} // 锁在此处完全释放
});
t.start();
}
}
4. 死锁及其预防
当多个线程相互等待对方持有的锁时,系统陷入停滞状态。
4.1 死锁示例
public class DeadlockDemo {
private static final Object resourceA = new Object();
private static final Object resourceB = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (resourceA) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (resourceB) {
System.out.println("Thread-1 执行");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (resourceB) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (resourceA) {
System.out.println("Thread-2 执行");
}
}
});
t1.start(); t2.start();
}
}
上述程序极有可能发生死锁:t1持有A等待B,t2持有B等待A。
4.2 死锁成因
- 互斥使用资源
- 不可抢占(持有者必须主动释放)
- 请求并保持(已持有一把锁还申请其他锁)
- 循环等待(形成闭环依赖)
4.3 预防策略
打破循环等待条件,约定统一的加锁顺序:
// 所有线程都按 A → B 的顺序加锁
synchronized (resourceA) {
synchronized (resourceB) {
// 安全操作
}
}
5. 内存可见性与 volatile 关键字
由于CPU缓存优化,一个线程对变量的修改可能无法立即被其他线程感知。
5.1 可见性问题演示
public class VisibilityProblem {
private static boolean running = true;
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
while (running) {
// 空循环
}
System.out.println("工作线程结束");
});
worker.start();
Thread.sleep(1000);
System.out.println("准备停止...");
running = false; // 主线程修改标志位
}
}
即使主线程将running设为false,worker线程仍可能持续运行——编译器将其优化为从寄存器读取而非内存。
5.2 volatile 解决方案
使用volatile禁止此类优化,强制每次访问都从主内存读取:
private static volatile boolean running = true;
添加volatile后,程序行为符合预期,worker线程会及时响应状态变化。
6. 线程间通信:wait/notify 机制
Java提供wait()、notify()和notifyAll()方法实现线程协作。
6.1 wait 方法
wait()必须在synchronized块中调用,它会:
- 释放当前持有的锁
- 使线程进入等待队列
- 被唤醒后重新竞争锁
public class WaitNotifyDemo {
private static final Object monitor = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waiter = new Thread(() -> {
synchronized (monitor) {
System.out.println("等待通知...");
try {
monitor.wait(); // 释放锁并等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("已被唤醒");
}
});
Thread notifier = new Thread(() -> {
synchronized (monitor) {
System.out.println("发出通知");
monitor.notify(); // 唤醒一个等待线程
}
});
waiter.start();
Thread.sleep(1000);
notifier.start();
waiter.join(); notifier.join();
}
}
6.2 notify 与 notifyAll
notify():随机唤醒一个等待线程notifyAll():唤醒所有等待线程(推荐用于避免遗漏)
带超时的wait(long timeout)可防止无限期阻塞。