文章目录
-
-
- [1. 竞态条件(Race Condition)](#1. 竞态条件(Race Condition))
- [2. 可见性问题(Visibility Problems)](#2. 可见性问题(Visibility Problems))
- [3. 原子性问题(Atomicity Problems)](#3. 原子性问题(Atomicity Problems))
- [4. 死锁(Deadlock)](#4. 死锁(Deadlock))
- [5. 活锁(Livelock)与饥饿(Starvation)](#5. 活锁(Livelock)与饥饿(Starvation))
- [6. 不可变对象(Immutable Objects)](#6. 不可变对象(Immutable Objects))
-
1. 竞态条件(Race Condition)
详细描述:
竞态条件是多线程环境中常见的错误源,当两个或多个线程以不可预测的顺序访问和修改共享资源时可能发生。这会导致程序行为依赖于线程调度的细节,从而导致不一致的状态。
代码示例与分析:
java
class Counter {
private int count = 0;
// 非同步方法,可能导致竞态条件
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class RaceConditionExample {
public static void main(String[] args) throws InterruptedException {
final 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("Final count: " + counter.getCount());
}
}
解决方法:
- 同步机制(Synchronization Mechanism) :使用
synchronized
关键字或显式锁(如ReentrantLock
),确保同一时间只有一个线程可以进入临界区。 - 原子类(Atomic Classes) :利用
java.util.concurrent.atomic
包提供的原子操作类,如AtomicInteger
、AtomicLong
等,它们保证了单个操作的原子性。
java
import java.util.concurrent.atomic.AtomicInteger;
class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子递增
}
public int getCount() {
return count.get();
}
}
2. 可见性问题(Visibility Problems)
详细描述:
可见性问题指的是一个线程对共享变量的更新对于其他线程来说不是立即可见的,因为每个线程都有自己的缓存副本。这可能造成数据不一致的问题。
代码示例与分析:
java
class SharedResource {
volatile boolean flag = false; // 使用volatile确保可见性
public void setFlagTrue() {
flag = true;
}
public boolean isFlagSet() {
return flag;
}
}
public class VisibilityProblemExample {
public static void main(String[] args) throws InterruptedException {
SharedResource resource = new SharedResource();
Thread t1 = new Thread(() -> {
while (!resource.isFlagSet()) {
// Busy-waiting until flag is set
}
System.out.println("Flag was set!");
});
t1.start();
Thread.sleep(100); // 模拟延迟
resource.setFlagTrue();
t1.join();
}
}
解决方法:
- 易失变量(Volatile Variable) :通过
volatile
关键字声明的变量会强制每次读取都从主内存获取最新值,并且每次写入都会立刻写回到主内存,保证所有线程都能看到最新的值。 - 内存屏障(Memory Barrier) :
volatile
变量的操作会在编译器和处理器层面插入内存屏障,防止指令重排序。
3. 原子性问题(Atomicity Problems)
详细描述:
非原子操作是指那些不能作为一个不可分割的整体执行的操作。例如,在递增计数器时,先读取旧值,再加1,最后写回新值;这个过程如果不加保护就可能被打断,导致部分完成的状态。
解决方法:
- CAS(Compare-and-Swap) :
Atomic
类内部通常使用CAS算法来实现高效且无锁的原子操作。 - 复合操作(Compound Actions) :对于复杂的原子操作,可以通过
synchronized
或者Lock
接口来确保整个操作作为一个整体被执行。
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class LockedCounter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
4. 死锁(Deadlock)
详细描述:
死锁是指两个或更多线程互相等待对方持有的锁,结果就是这些线程都无法继续执行下去。这种情况通常发生在两个或更多线程按照不同的顺序获取相同的锁时。
代码示例与分析:
java
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock 1...");
try { Thread.sleep(10); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock 2...");
try { Thread.sleep(10); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread 2: Holding lock 2 & 1...");
}
}
});
t1.start();
t2.start();
}
}
预防死锁的方法:
- 锁排序(Lock Ordering):总是按照相同的顺序获取锁,避免循环等待。
- 超时机制(Timeout Mechanism):尝试获取锁失败后可以选择放弃,避免无限期等待。
- 非阻塞算法(Non-blocking Algorithms):设计算法时尽量减少锁定的需求,使用无锁数据结构或CAS操作。
5. 活锁(Livelock)与饥饿(Starvation)
详细描述:
- 活锁:线程虽然没有被阻塞,但是一直忙于响应其他线程的动作而无法做有用的工作。
- 饥饿:某些线程由于总是得不到所需的资源而无法获得进展。
代码示例与分析:
java
public class LivelockExample {
private static boolean flipFlop = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (flipFlop) {
// yield to let the other thread run
Thread.yield();
}
System.out.println("Thread 1 done.");
});
Thread t2 = new Thread(() -> {
while (!flipFlop) {
// yield to let the other thread run
Thread.yield();
}
System.out.println("Thread 2 done.");
});
flipFlop = true;
t1.start();
t2.start();
}
}
缓解措施:
- 公平锁(Fair Lock) :如
ReentrantLock
支持公平模式,使得等待最久的线程优先获得锁。 - 调度策略(Scheduling Policy):设计合理的调度算法,定期检查是否有线程处于饥饿状态,并给予适当的机会。
6. 不可变对象(Immutable Objects)
详细描述:
创建不可变对象的一个关键是确保对象一旦构造完成就不能改变其内部状态。不可变对象具有以下特点:
- 它们是线程安全的,不需要额外的同步。
- 它们可以自由共享,不会引发副作用。
- 它们通常比可变对象更容易理解和维护。
代码示例与分析:
java
public final class ImmutableObject {
private final String value;
public ImmutableObject(String value) {
this.value = value;
}
public String getValue() {
return value;
}
// No setter methods, ensuring immutability
}
专业术语:
- 防御性拷贝(Defensive Copy):在返回或接受外部传入的对象时,应该创建该对象的副本,而不是直接暴露或接收原始对象引用,以保持不可变性。
- final修饰符(Final Modifier) :使用
final
关键字修饰类和成员变量,确保它们在实例化之后不能被修改。