模式切换
线程安全问题
在多线程环境下,线程安全是一个非常重要的问题。如果多个线程同时访问共享资源,并且没有正确的同步机制,那么可能会导致数据不一致、程序运行异常等问题。
什么是线程安全?
线程安全的定义
线程安全是指多个线程同时访问共享资源时,程序的行为仍然是正确的,不会出现数据不一致或其他异常情况。
线程安全的代码可以在多线程环境下正确运行,而不需要额外的同步措施。
竞态条件(Race Condition)
竞态条件是指多个线程同时访问共享资源,并且最终的结果依赖于线程执行的顺序。
在下面的示例代码中,如果多个线程同时调用 increment()
方法,count++
的操作可能会被多个线程交错执行,导致最终结果不正确。
java
public class Counter {
private int count = 0;
public void increment() {
count++; // 非原子操作
}
public int getCount() {
return count;
}
}
共享资源的线程安全问题
- 贡献资源可以是变量、对象、文件、数据库连接等。
- 如果多个线程同时修改共享资源,可能会导致数据不一致或其他问题。
例如在下方示例代码中,如果多个线程同时调用 setValue()
和 getValue()
方法,可能会导致读取到错误的值。
java
public class SharedResource {
private int value = 0;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
同步机制
synchronized 关键字
synchronized
关键字可以用来修饰方法或代码块,确保同一时间只有一个线程可以执行被修改的代码。
- 同步方法:使用
synchronized
修饰实例方法或静态方法。
在下方示例代码中,同一时间只有一个线程可以执行 increment()
或 getCount()
方法。
java
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
- 同步代码块:使用
synchronized
修饰代码块,并指定锁对象。
在下方示例代码中,只有持有 lock
对象的线程可以执行同步代码块。
java
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
}
锁的概念与使用
锁是一种同步机制,用于控制对共享资源的访问。
Java 中提供了 ReentrantLock
等显式锁,可以更灵活地控制同步。
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
volatile 关键字
- 保证可见性
volatile
关键字用于修饰变量,确保变量的修改对所有线程可见。
当一个线程修改了 volatile
修饰的变量的值,其他线程可以立即看到修改后的值。
java
public class SharedResource {
private volatile boolean flag = false;
public void setFlag(boolean flag) {
this.flag = flag;
}
public boolean getFlag() {
return flag;
}
}
- 禁止指令重排序
volatile
关键字还可以禁止 JVM 对指令进行重排序,确保代码的执行顺序与编写顺序一致。
java
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
原子操作
原子类
java.util.concurrent.atomic
包中的提供了一系列的原子类,如 AtomicInteger
、AtomicReference
等,用于实现无锁的线程安全操作。
java
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
CAS(Compare-And-Swap)机制
CAS 是一种无锁的同步机制,通过比较并交换的方式实现线程安全。
CAS 操作包括三个操作数:内存值、预期值和新值。只有当内存值等于预期值时,才会将内存值更新为新值。
java
public class CASExample {
private volatile int value;
public void increment() {
int oldValue;
int newValue;
do {
oldValue = value;
newValue = oldValue + 1;
} while (!compareAndSet(oldValue, newValue));
}
private synchronized boolean compareAndSet(int oldValue, int newValue) {
if (value == oldValue) {
value = newValue;
return true;
}
return false;
}
}
总结
- 线程安全是指多线程环境下程序的正确性。
- 竞态条件和共享资源访问是导致线程安全问题的主要原因。
- 同步机制(如
synchronized
和锁)可以解决线程安全问题。 volatile
关键字可以保证变量的可见性和禁止指令重排序。- 原子类和 CAS 机制提供无锁的线程安全操作。