那些年踩过的线程不安全陷阱:层出不穷的真实案例
2023-10-31 20:29:04
对象引用的逸出
假设你有一个发布任务的系统,其中包含一个 Task
类来封装任务信息。出于性能考虑,你在 Task
类中保存了一个对任务执行者的引用。当任务被发布时,系统会创建一个新的线程来执行任务。在执行过程中,任务执行者可能会引用 Task
类中的变量。
public class Task {
private Executor executor;
public Task(Executor executor) {
this.executor = executor;
}
public void execute() {
executor.execute();
}
}
现在,问题来了。如果在执行任务时,任务执行者被其他线程修改了,可能会导致线程不安全问题。例如,如果任务执行者被另一个线程设置为 null
,那么在执行任务时就会抛出 NullPointerException
异常。
隐藏的逸出
隐藏的逸出是指,虽然变量本身没有被共享,但由于其他变量的共享,导致该变量也被共享了。
举个例子,假设你在 Task
类中有一个私有变量 count
,用来统计任务执行的次数。你认为,由于 count
是私有的,因此它是线程安全的。但实际上,由于 Task
类是可变的,因此其他线程可以通过修改 Task
类来修改 count
的值。
public class Task {
private int count;
public void execute() {
count++;
}
}
多个线程操作同一非线程安全对象
假设你有一个 Counter
类,用来统计一个数字。Counter
类包含一个私有变量 count
,用来存储数字的值。Counter
类还提供了一个 increment()
方法,用于增加 count
的值。
public class Counter {
private int count;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
现在,假设你创建了两个线程,同时调用 Counter
类的 increment()
方法。由于 Counter
类是非线程安全的,因此两个线程可能会同时修改 count
的值,导致最终的 count
值不正确。
使用不同的锁锁定同一对象
假设你有一个 Account
类,用来管理一个银行账户。Account
类包含一个私有变量 balance
,用来存储账户的余额。Account
类还提供了一个 withdraw()
方法,用于从账户中提取钱。
public class Account {
private int balance;
public void withdraw(int amount) {
synchronized (this) {
if (balance >= amount) {
balance -= amount;
}
}
}
}
现在,假设你创建了两个线程,同时调用 Account
类的 withdraw()
方法。由于两个线程使用了不同的锁来锁定 Account
类,因此可能会出现线程不安全问题。例如,如果两个线程同时调用 withdraw()
方法,并且提取的金额之和大于账户的余额,那么其中一个线程可能会提取到比账户余额更多的钱。
同时操作多个关联的线程安全对象
假设你有一个 Order
类,用来管理一个订单。Order
类包含一个私有变量 items
,用来存储订单中的商品。Order
类还提供了一个 addItem()
方法,用于向订单中添加商品。
public class Order {
private List<Item> items;
public void addItem(Item item) {
synchronized (this) {
items.add(item);
}
}
}
现在,假设你创建了两个线程,同时调用 Order
类的 addItem()
方法。由于两个线程使用了同一个锁来锁定 Order
类,因此不会出现线程不安全问题。但是,如果两个线程同时调用 Order
类的 getItems()
方法,那么可能会出现线程不安全问题。因为 getItems()
方法并没有对 Order
类加锁,因此两个线程可能会同时修改 items
列表,导致最终的 items
列表不正确。
总结
线程不安全是一个非常复杂的问题,而且在实际开发中经常遇到。因此,在开发多线程程序时,一定要注意线程安全问题。要做到这一点,你可以使用以下几种方法:
- 使用同步机制来保护共享数据。
- 使用不变对象来避免共享数据。
- 使用线程安全的类库。
只有这样,才能避免线程不安全问题,保证程序的正确性和可靠性。