假设客户有一个Credit Card
。他的余额为x
,他正在购买y
有价值的商品(y<x)
。他将再次购买另一件物品,费用为z
。
(y+z>x but z<x
)。
现在我将在Java
中模拟这种情况。如果所有事务都按顺序发生,则无需恐慌。客户可以购买y
有价值的商品,然后他没有足够的信用额度购买其他商品。
但是当我们进入多线程环境时,我们必须处理一些锁定机制或某些策略。因为如果其他一些线程读取信用卡对象之前反映的变化,以前的线程严重问题将会上升。
据我所知,一种方法是我们可以保留原始余额的副本,我们可以在更新余额之前检查当前值。如果值与原始值相同,那么我们可以确保其他线程不会更改余额。如果平衡不同,那么我们必须撤消计算。
Java Synchronization也是一个很好的解决方案。现在我的问题是在这种情况下实施的最佳方法是什么?
此外,如果我们要全面了解这一点。同步会影响系统的性能。由于它被锁定,因此对象和其他线程必须等待。
答案 0 :(得分:3)
我更愿意拥有ReadWriteLock
,这有助于锁定它进行读写,这很好,因为你可以为每个资源分别进行读写锁定:
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.readLock().lock();
// multiple readers can enter this section
// if not locked for writing, and not writers waiting
// to lock for writing.
readWriteLock.readLock().unlock();
readWriteLock.writeLock().lock();
// only one writer can enter this section,
// and only if no threads are currently reading.
readWriteLock.writeLock().unlock();
ReadWriteLock
在内部保留两个Lock实例。一个保护读访问权限,一个保护写访问权。
答案 1 :(得分:2)
您的提案不合适。您无法确定在检查和更新之间是否发生上下文切换。
唯一的方法是同步。
答案 2 :(得分:2)
你在说什么听起来像software transactional memory.你乐观地认为没有其他线程会修改你的交易所依赖的数据,但你有一个机制来检测它们是否有。
java.util.concurrent.atomic
包中的类型可以帮助构建无锁解决方案。他们实施有效的比较和交换操作。例如,AtomicInteger
引用将允许您执行以下操作:
AtomicInteger balance = new AtomicInteger();
…
void update(int change) throws InsufficientFundsException {
int original, updated;
do {
original = balance.get();
updated = original + change;
if (updated < 0)
throw new InsufficientFundsException();
} while (!balance.compareAndSet(original, update));
}
正如您所看到的,这种方法受制于活锁线程条件,其他线程不断更改平衡,导致一个线程永远循环。实际上,您的应用程序的细节决定了活锁的可能性。
显然,这种方法很复杂,并且存在陷阱。如果您不是并发专家,那么使用锁来提供原子性会更安全。如果synchronized
块内的代码不执行任何阻塞操作(如I / O),则锁定通常表现得足够好。如果关键部分中的代码具有明确的执行时间,那么最好使用锁定。
答案 3 :(得分:0)
据我所知,我们可以保留一份原始余额 我们可以在更新余额之前检查当前值。如果 值与原始值相同,那么我们可以确定其他线程 不会改变平衡。如果平衡不同,那么我们必须撤消 我们的计算。
听起来像AtomicInteger.compareAndSet()
和AtomicLong.compareAndSet()
那样。
更容易理解的方法是在您的synchronized
类上使用CreditCard
方法,您的代码会调用这些方法来更新余额。 (对象上只能有一个synchronized
方法可以执行。)
在这种情况下,听起来您希望public synchronized boolean makePurchase(int cost)
方法在成功时返回true
,在失败时返回false
。目标是您的对象上的任何事务都不需要多个方法调用 - 正如您已经意识到的那样,您不希望在CreditCard
(getBalance()
和更晚{{1}上进行两次方法调用因为潜在的竞争条件而进行交易。