实现不超卖是一项确保在高并发场景下正确处理库存等关键数据的问题。为了防止超卖,可以从多个层面(如Java锁、数据库、Redis等)进行处理。以下是详细的实现步骤和相关代码示例。
1. Java锁机制
1.1 使用 synchronized 关键字
synchronized 是Java中的基础锁机制,适用于单实例场景:
public class InventoryService {
private int stock = 100;
public synchronized boolean purchaseItem() {
if (stock > 0) {
stock--;
return true;
}
return false;
}
}
1.2 使用 ReentrantLock
ReentrantLock 提供了更灵活的锁机制,适用于更复杂的并发控制:
import java.util.concurrent.locks.ReentrantLock;
public class InventoryService {
private int stock = 100;
private final ReentrantLock lock = new ReentrantLock();
public boolean purchaseItem() {
lock.lock();
try {
if (stock > 0) {
stock--;
return true;
}
return false;
} finally {
lock.unlock();
}
}
}
2. 数据库层面
2.1 悲观锁
使用 SELECT ... FOR UPDATE 语句锁定行数据:
START TRANSACTION;
SELECT stock FROM inventory WHERE item_id = 1 FOR UPDATE;
UPDATE inventory
SET stock = stock - 1
WHERE item_id = 1 AND stock > 0;
COMMIT;
2.2 乐观锁
使用版本号或时间戳实现:
UPDATE inventory
SET stock = stock - 1, version = version + 1
WHERE item_id = 1 AND stock > 0 AND version = :version;
在Java中实现:
public boolean purchaseItem(int itemId, int version) {
String sql = "UPDATE inventory SET stock = stock - 1, version = version + 1 WHERE item_id = ? AND stock > 0 AND version = ?";
int rowsUpdated = jdbcTemplate.update(sql, itemId, version);
return rowsUpdated > 0;
}
3. Redis层面
3.1 使用 Redis 作为分布式锁
使用 SETNX 和 EXPIRE 实现分布式锁:
public boolean purchaseItem() {
String lockKey = "lock:inventory:1";
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);
if (!locked) {
return false;
}
try {
int stock = Integer.parseInt(redisTemplate.opsForValue().get("inventory:1"));
if (stock > 0) {
redisTemplate.opsForValue().decrement("inventory:1");
return true;
}
return false;
} finally {
redisTemplate.delete(lockKey);
}
}
3.2 使用 Redis 的原子操作
使用 Lua 脚本保证操作的原子性:
String luaScript =
"if (redis.call('get', KEYS[1]) > '0') then " +
" redis.call('decr', KEYS[1]); " +
" return 1; " +
"else " +
" return 0; " +
"end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList("inventory:1"));
return result == 1;
综合考虑
在实际生产环境中,可能需要结合以上多种方法以应对不同的场景。例如,使用数据库的乐观锁和Redis的分布式锁结合,以保证在高并发情况下的库存一致性和高性能。
以上实现方法可根据实际业务需求进行调整和优化,确保系统在高并发环境下稳定可靠地运行。
cas 与ReentrantLock
1. 定义和实现机制
ReentrantLock
ReentrantLock 是Java中的一种显式锁(Explicit Lock),提供了比 synchronized 更加灵活的锁机制。它是基于互斥锁(Mutual Exclusion)来实现的,通过 lock() 和 unlock() 方法来显式地获取和释放锁。
import java.util.concurrent.locks.ReentrantLock;
public class InventoryService {
private final ReentrantLock lock = new ReentrantLock();
public void performAction() {
lock.lock();
try {
// critical section
} finally {
lock.unlock();
}
}
}
CAS (Compare-And-Swap)
CAS 是一种原子操作,用于实现无锁的并发数据结构。它通过比较并交换变量的值来实现同步,通常用于实现乐观锁(Optimistic Locking)。
import java.util.concurrent.atomic.AtomicInteger;
public class InventoryService {
private final AtomicInteger stock = new AtomicInteger(100);
public boolean purchaseItem() {
int current;
do {
current = stock.get();
if (current <= 0) {
return false;
}
} while (!stock.compareAndSet(current, current - 1));
return true;
}
}
2. 适用场景
ReentrantLock
- 适用于需要显式锁操作的场景,如需要尝试获取锁、定时获取锁、中断锁等待等。
- 适合复杂的并发控制,例如需要条件变量(Condition)来协调多个线程。
CAS
- 适用于无锁数据结构和算法,适合高并发情况下的乐观锁操作。
- 适合简单的原子操作,例如计数器、自增变量等。
3. 性能对比
ReentrantLock
- 由于是基于互斥锁,可能会导致线程阻塞和上下文切换,开销较大。
- 适用于低到中等并发量的场景。
CAS
- 无锁操作,通常性能更高,因为避免了线程阻塞和上下文切换。
- 在高并发场景下,能显著提高性能。
- 可能会导致自旋(Spin)等待,若CAS操作失败需要不断重试,可能导致CPU资源浪费。
4. 公平性和可重入性
ReentrantLock
- 支持公平锁(Fair Lock)和非公平锁(Non-fair Lock)。
- 支持可重入,即同一线程可以多次获取同一个锁。
CAS
- 不涉及锁机制,因此不存在公平性问题。
- 不支持可重入,因为每次CAS操作都是独立的。
5. 使用复杂性
ReentrantLock
- 需要显式获取和释放锁,使用较为复杂。
- 需要处理锁的释放,以避免死锁和资源泄漏。
CAS
- 操作简单,通常使用原子变量类(如
AtomicInteger、AtomicReference)即可。 - 需要正确处理CAS失败的重试逻辑。
总结
ReentrantLock 和 CAS 各有优缺点,适用的场景也不同。ReentrantLock 更加灵活,适用于需要复杂并发控制的场景,但性能较低。而 CAS 适用于高并发、简单原子操作的场景,性能较高,但需要注意处理自旋重试带来的CPU开销。
选择哪种机制应根据具体业务需求、并发量以及系统性能要求来决定。在某些复杂场景下,可能需要将两者结合使用,以实现最佳的并发控制效果。