在 Java 并发编程中,线程安全是一个非常重要的概念。如果多个线程同时访问一个共享资源而不进行适当的同步,就会出现线程安全问题,导致程序行为异常。根据不同的场景,线程安全问题可以分为 运行结果错误、发布和初始化导致的线程安全问题 和 活跃性问题。本文将详细探讨这三类线程安全问题,并通过实例分析它们的产生原因和解决方案。
1. 运行结果错误
问题分析:
运行结果错误通常发生在多个线程并发执行时,其中一个线程对共享变量进行修改,而其他线程在修改过程中也对该变量进行读写操作。由于操作不具备原子性,导致最终结果不符合预期。
代码示例:
public class WrongResult {
volatile static int i;
public static void main(String[] args) throws InterruptedException {
Runnable r = new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
i++; // 注意:这不是原子操作,分为 读取 -> 加1 -> 保存 三个步骤
}
}
};
Thread thread1 = new Thread(r);
thread1.start();
Thread thread2 = new Thread(r);
thread2.start();
thread1.join();
thread2.join();
System.out.println(i); // 本来想要输出 20000,但实际结果不一定是 20000
}
}
问题原因:
i++
其实是三步操作:读取i
、增加 1、写回i
。这三步操作并不是原子操作,因此在线程切换的过程中,可能会发生线程间的竞争,导致丢失更新。
解决方案:
- 使用
synchronized
或Atomic
类来保证操作的原子性。例如,可以使用AtomicInteger
类来替代普通的int
,确保线程安全。
import java.util.concurrent.atomic.AtomicInteger;
public class CorrectResult {
static AtomicInteger i = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Runnable r = () -> {
for (int j = 0; j < 10000; j++) {
i.incrementAndGet(); // 原子操作,保证线程安全
}
};
Thread thread1 = new Thread(r);
thread1.start();
Thread thread2 = new Thread(r);
thread2.start();
thread1.join();
thread2.join();
System.out.println(i); // 现在会正确输出 20000
}
}
2. 发布和初始化导致的线程安全问题
问题分析:
当一个对象在多线程环境中被多个线程访问时,必须确保对象的初始化操作是线程安全的。如果在对象初始化的过程中,其他线程就开始访问该对象,可能会导致未初始化完成的对象被使用,从而产生线程安全问题。
代码示例:
public class WrongInit {
private Map<Integer, String> students;
public WrongInit() {
new Thread(() -> {
students = new HashMap<>();
students.put(1, "王小美");
students.put(2, "钱二宝");
students.put(3, "周三");
students.put(4, "赵四");
}).start();
}
public Map<Integer, String> getStudents() {
return students;
}
public static void main(String[] args) throws InterruptedException {
WrongInit wrongInit = new WrongInit();
System.out.println(wrongInit.getStudents().get(1)); // 可能抛出空指针异常
}
}
问题原因:
students
在WrongInit
类的构造方法中被异步初始化。如果在初始化过程中调用了getStudents()
方法,可能会得到一个尚未初始化完成的对象,从而导致NullPointerException
或其他异常。
解决方案:
- 确保对象在多线程访问前已完全初始化。可以通过
volatile
或synchronized
保证对象的正确发布,或采用final
修饰来确保对象初始化完成后不会被修改。
public class CorrectInit {
private volatile Map<Integer, String> students;
public CorrectInit() {
new Thread(() -> {
students = new HashMap<>();
students.put(1, "王小美");
students.put(2, "钱二宝");
students.put(3, "周三");
students.put(4, "赵四");
}).start();
}
public Map<Integer, String> getStudents() {
return students;
}
public static void main(String[] args) throws InterruptedException {
CorrectInit correctInit = new CorrectInit();
// 使用阻塞等待或其他方式确保对象已完全初始化
Thread.sleep(500);
System.out.println(correctInit.getStudents().get(1)); // 不会抛出空指针异常
}
}
3. 活跃性问题
活跃性问题是指线程无法按预期完成任务或获取资源,通常表现为 死锁、活锁 和 饥饿。
死锁(Deadlock)
死锁发生时,两个或多个线程因互相等待对方释放资源而导致程序无法继续执行。
代码示例:死锁
public class MayDeadLock {
Object o1 = new Object();
Object o2 = new Object();
public void thread1() throws InterruptedException {
synchronized (o1) {
Thread.sleep(500);
synchronized (o2) {
System.out.println("线程1成功拿到两把锁");
}
}
}
public void thread2() throws InterruptedException {
synchronized (o2) {
Thread.sleep(500);
synchronized (o1) {
System.out.println("线程2成功拿到两把锁");
}
}
}
public static void main(String[] args) {
MayDeadLock mayDeadLock = new MayDeadLock();
new Thread(() -> {
try {
mayDeadLock.thread1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
mayDeadLock.thread2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
死锁产生的条件:
- 互斥:至少有一个资源处于被占用状态。
- 不可剥夺:资源不能强行抢占。
- 请求与保持:线程已经持有某些资源,且正在等待其他资源。
- 循环等待:形成了资源请求的环状链条。
解决方案:
- 避免线程持有多个锁。
- 采用定时锁等待(
tryLock()
)来避免死锁。
活锁(Livelock)
活锁与死锁非常相似,区别在于线程不断改变状态,但永远无法完成任务。尽管线程没有被阻塞,但它们无法获得所需的资源。
饥饿(Starvation)
饥饿发生时,某些线程始终得不到 CPU 资源,导致无法执行。常见的原因是线程优先级不均或锁竞争过于激烈。
总结
线程安全问题在多线程环境中经常出现,理解和处理这些问题是并发编程的关键。常见的线程安全问题包括:
- 运行结果错误:如竞争条件导致的错误结果,解决方法是使用原子操作或同步机制。
- 发布和初始化导致的线程安全问题:确保对象在被使用前已完全初始化。
- 活跃性问题:如死锁、活锁和饥饿,需要避免复杂的锁嵌套,并合理调度线程。
掌握这些线程安全问题及其解决方案,能够帮助你写出更高效且可靠的并发程序。
标签:初始化,Java,Thread,students,实例,线程,new,public From: https://blog.csdn.net/fulai00/article/details/143801502