所属分类:资源竞争
缺陷详解
软件在访问共享资源时,如果未正确获取或释放锁,可能会导致以下问题:
- 资源状态异常:由于多个线程并发访问资源,未正确同步可能引发数据不一致。
- 行为异常:在资源尚未完成操作时切换线程,可能引发未定义行为。
- 死锁和竞争条件:同步对象设计不当可能导致系统阻塞或资源访问冲突。
导致结果和风险
- 资源竞争:导致数据一致性问题或异常行为。
- 死锁风险:多个线程争用锁可能引发死锁,导致系统功能瘫痪。
- 性能问题:过度同步或锁使用不当可能降低系统效率。
缓解和预防措施
- 避免使用公共访问权限的同步对象。
- 避免使用字符串变量作为锁对象,因其可能被多个线程意外共享。
- 使用专用锁对象(如 ReentrantLock)明确线程同步边界。
- 使用适当的锁粒度,避免过度同步导致性能下降。
- 定期检查可能的死锁或竞争情况,确保锁的正确使用。
- 利用现代工具和框架(如 Java 的 java.util.concurrent 包)实现线程安全。
示例分析
示例 1:未同步的 long 字段
问题代码
public class UnsyncedLongExample {
private long someLongValue;
public long getLongValue() {
return someLongValue;
}
public void setLongValue(long value) {
someLongValue = value;
}
}
漏洞分析
- 问题:long 类型字段的操作非原子性。多线程访问时可能导致数据不一致。
- 风险:可能发生部分写入或读取,导致逻辑错误。
修复后的代码
public class SyncedLongExample {
private long someLongValue;
public synchronized long getLongValue() {
return someLongValue;
}
public synchronized void setLongValue(long value) {
someLongValue = value;
}
}
示例 2:双重检查锁定(线程安全)
问题代码
public class UnsafeLazySingleton {
private Helper helper;
public Helper getHelper() {
if (helper == null) {
synchronized (this) {
if (helper == null) {
helper = new Helper(); // 未正确处理线程同步
}
}
}
return helper;
}
}
漏洞分析
- 问题:构造函数调用可能被打断,导致对象部分初始化。
- 风险:其他线程访问尚未完全初始化的对象,可能引发异常。
修复后的代码
public class SafeLazySingleton {
private volatile Helper helper;
public Helper getHelper() {
if (helper == null) {
synchronized (this) {
if (helper == null) {
helper = new Helper(); // 线程安全
}
}
}
return helper;
}
}
示例 3:银行账户操作的资源竞争
问题代码
public class BankAccount {
private double accountBalance;
public void deposit(double amount) {
accountBalance += amount; // 非线程安全
}
public void withdraw(double amount) {
accountBalance -= amount; // 非线程安全
}
}
漏洞分析
- 问题:deposit 和 withdraw 方法未同步,导致多个线程访问时可能修改余额冲突。
- 风险:账户余额可能因资源竞争变为负数或产生错误结果。
修复后的代码
方法 1:使用同步方法
public class SyncedBankAccount {
private double accountBalance;
public synchronized void deposit(double amount) {
accountBalance += amount;
}
public synchronized void withdraw(double amount) {
accountBalance -= amount;
}
}
方法 2:使用 ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockedBankAccount {
private double accountBalance;
private final Lock lock = new ReentrantLock();
public void deposit(double amount) {
lock.lock();
try {
accountBalance += amount;
} finally {
lock.unlock();
}
}
public void withdraw(double amount) {
lock.lock();
try {
accountBalance -= amount;
} finally {
lock.unlock();
}
}
}
示例 4:条件锁的使用
问题代码
public class BankAccount {
private double balance;
public void withdraw(double amount) throws InterruptedException {
if (balance < amount) {
wait(); // 未正确释放锁,可能导致死锁
}
balance -= amount;
}
public void deposit(double amount) {
balance += amount;
notifyAll(); // 未明确范围,可能误唤醒其他线程
}
}
修复后的代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class SafeBankAccount {
private double balance;
private final ReentrantLock lock = new ReentrantLock();
private final Condition sufficientFundsCondition = lock.newCondition();
public void deposit(double amount) {
lock.lock();
try {
balance += amount;
sufficientFundsCondition.signalAll();
} finally {
lock.unlock();
}
}
public void withdraw(double amount) throws InterruptedException {
lock.lock();
try {
while (balance < amount) {
sufficientFundsCondition.await();
}
balance -= amount;
} finally {
lock.unlock();
}
}
}
建议与总结
- 避免常见错误
- 不要将 String 或公共对象用作锁。
- 确保锁定范围最小化,避免性能损失或死锁。
- 使用现代工具
- 使用 java.util.concurrent.locks 包提供的功能。
- 利用框架支持的线程安全机制。
- 强化代码审查
- 定期检查关键业务代码,确保无潜在竞争条件或死锁。
- 参考
通过正确使用锁和同步机制,可以有效避免资源竞争问题,提升系统的稳定性和性能。
工作:SAST工具推介、评测、代码审计、培训资料、应用安全咨询、SAST检测规则、安全漏洞数据处理、许可证数据处理、组件数据处理等。