gav*_*koa 43 java multithreading
如何将资金从一个账户转移到另一个账户?对于:
public class Account {
public Account(BigDecimal initialAmount) {...}
public BigDecimal getAmount() {...}
public void setAmount(BigDecimal amount) {...}
}
Run Code Online (Sandbox Code Playgroud)
我希望伪代码:
public boolean transfer(Account from, Account to, BigDecimal amount) {
BigDecimal fromValue = from.getAmount();
if (amount.compareTo(fromValue) < 0)
return false;
BigDecimal toValue = to.getAmount();
from.setAmount(fromValue.add(amount.negate()));
to.setAmount(toValue.add(amount));
return true;
}
Run Code Online (Sandbox Code Playgroud)
在多线程环境中安全地更新帐户,我看到危险情况如下:
acc1 --> acc2 || acc2 --> acc1
acc1 --> acc2 || acc2 --> acc3 || acc3 --> acc1
...
Run Code Online (Sandbox Code Playgroud)
最简单的解决方案是对共享对象进行阻止,但对于以下情况来说效率低下:
acc1 --> acc2 || acc3 --> acc4 and acc1 != acc3 and acc2 != acc4
Run Code Online (Sandbox Code Playgroud)
我希望独立移动是并行执行的.
更新似乎建议的解决方案:
synchronize (acc1) {
synchronize (acc2) {
....
}
}
Run Code Online (Sandbox Code Playgroud)
导致死锁,因为顺序获得2个锁...
更新2 你对"在多线程环境中安全地更新帐户"的意思是什么?是唯一担心帐户不会最终有减去资金还是有其他问题?
如果acc1(2); acc2(3)和acc1 --1--> acc2和acc2 --2--> acc1我预期的一致性:(acc1, acc2)具有(3, 2)价值,而不是(4, 2)或(3, 4).总数应为5,而不是1 + 3 = 4或4 + 3 = 7.
你一次期望多少并发交易?1000-10000 - 因此锁定共享对象效率不高.
Pet*_*etr 45
一个简单的解决方案可能是每个帐户使用一个锁,但为了避免死锁,您必须始终以相同的顺序获取锁.因此,您可以拥有最终帐户ID,并首先使用较少的ID获取该帐户的锁定:
public void transfer(Account acc1, Account acc2, BigDecimal value) {
Object lock1 = acc1.ID < acc2.ID ? acc1.LOCK : acc2.LOCK;
Object lock2 = acc1.ID < acc2.ID ? acc2.LOCK : acc1.LOCK;
synchronized (lock1) {
synchronized (lock2) {
acc1.widrawal(value);
acc2.send(value);
}
}
}
Run Code Online (Sandbox Code Playgroud)
Lie*_*yan 12
一种方法是使用事务日志.在转移资金之前,您需要写入您想要做的每个帐户的交易日志.日志应包含:从帐户中取出/取出的金额,以及在日志对之间共享的锁定.
最初锁定应处于阻塞状态.您创建了一个日志对,一个数量为X,另一个数量为-X,两者共享一个锁.然后将日志条目发送到相应帐户的收件箱,从中取出资金的帐户应保留该金额.一旦您确认他们已安全送达,然后释放锁.锁定被释放的那一刻,如果没有回复,你就处于某一点.然后帐户应该自行解决.
如果任何一方想要在锁定释放之前的任何时间使事务失败,那么只需删除日志并将保留金额返回到主余额.
这种方法可能有点沉重,但它也可以在分布式场景中工作,其中帐户实际上在不同的机器中,并且实际上必须保留收件箱,以确保如果任何机器崩溃/去,钱永远不会丢失意外离线.其一般技术称为两相锁定.
我建议创建一个方法Account.withdraw(amount),如果它没有足够的资金,它会抛出异常.此方法需要在帐户本身上同步.
编辑:
还需要一个在接收帐户实例上同步的Account.deposit(amount)方法.
基本上这将导致在撤销时锁定第一个帐户,然后在存款时锁定接收帐户.所以两个锁但不是同时.
代码示例:假设撤销/存储是同步的并返回布尔成功状态而不是抛出异常.
public boolean transfer(Account from, Account to, BigDecimal amount) {
boolean success = false;
boolean withdrawn = false;
try {
if (from.withdraw(amount)) {
withdrawn = true;
if (to.deposit(amount)) {
success = true;
}
}
} finally {
if (withdrawn && !success) {
from.deposit(amount);
}
}
return success;
}
Run Code Online (Sandbox Code Playgroud)
您可以创建一个Account T仅用于转移资金的额外存款.因此,如果你想转移A到B你实际转移A到T然后转移T到B.对于每个转移,您只能锁定A或B取决于参与转移的帐户.由于您使用相同类型进行传输,因此最终只需要很少的额外代码,因此维护成本较低.
要减少额外帐户的数量,您可以将它们保存在池中.如果您有一个正在处理传输的线程池,那么您可以为每个线程分配它自己的额外帐户.因此,您不需要经常从池中请求和释放这些额外的帐户.
一种方法是使用一种"条带锁",其中锁定/解锁方法在几个锁上运行.帐户映射到锁使用hashCode,您分配的锁越多,您获得的并行性就越多.
这是代码示例:
public class StripedLock {
private final NumberedLock[] locks;
private static class NumberedLock {
private final int id;
private final ReentrantLock lock;
public NumberedLock(int id) {
this.id = id;
this.lock = new ReentrantLock();
}
}
/**
* Default ctor, creates 16 locks
*/
public StripedLock() {
this(4);
}
/**
* Creates array of locks, size of array may be any from set {2, 4, 8, 16, 32, 64}
* @param storagePower size of array will be equal to <code>Math.pow(2, storagePower)</code>
*/
public StripedLock(int storagePower) {
if (!(storagePower >= 1 && storagePower <= 6)) { throw new IllegalArgumentException("storage power must be in [1..6]"); }
int lockSize = (int) Math.pow(2, storagePower);
locks = new NumberedLock[lockSize];
for (int i = 0; i < locks.length; i++)
locks[i] = new NumberedLock(i);
}
/**
* Map function between integer and lock from locks array
* @param id argument
* @return lock which is result of function
*/
private NumberedLock getLock(int id) {
return locks[id & (locks.length - 1)];
}
private static final Comparator<? super NumberedLock> CONSISTENT_COMPARATOR = new Comparator<NumberedLock>() {
@Override
public int compare(NumberedLock o1, NumberedLock o2) {
return o1.id - o2.id;
}
};
public void lockIds(@Nonnull int[] ids) {
Preconditions.checkNotNull(ids);
NumberedLock[] neededLocks = getOrderedLocks(ids);
for (NumberedLock nl : neededLocks)
nl.lock.lock();
}
public void unlockIds(@Nonnull int[] ids) {
Preconditions.checkNotNull(ids);
NumberedLock[] neededLocks = getOrderedLocks(ids);
for (NumberedLock nl : neededLocks)
nl.lock.unlock();
}
private NumberedLock[] getOrderedLocks(int[] ids) {
NumberedLock[] neededLocks = new NumberedLock[ids.length];
for (int i = 0; i < ids.length; i++) {
neededLocks[i] = getLock(i);
}
Arrays.sort(neededLocks, CONSISTENT_COMPARATOR);
return neededLocks;
}
}
// ...
public void transfer(StripedLock lock, Account from, Account to) {
int[] accountIds = new int[]{from.getId(), to.getId()};
lock.lockIds(accountIds);
try {
// profit!
} finally {
lock.unlockIds(accountIds);
}
}
Run Code Online (Sandbox Code Playgroud)
如前所述,您应该锁定两个帐户,始终以相同的顺序锁定。然而,关键部分是确保整个 VM 实例的高粒度和单一性。这可以使用String.intern():
public boolean transfer(Account from, Account to, BigDecimal amount) {
String fromAccountId = from.id.toString().intern();
String toAccountId = to.id.toString().intern();
String lock1, lock2;
if (from.id < to.id) {
lock1 = fromAccountId;
lock2 = toAccountId;
} else {
lock1 = toAccountId;
lock2 = fromAccountId;
}
// synchronizing from this point, since balances are checked
synchronized(lock1) {
synchronized(lock2) {
BigDecimal fromValue = from.getAmount();
if (amount.compareTo(fromValue) < 0)
return false;
BigDecimal toValue = to.getAmount();
from.setAmount(fromValue.add(amount.negate()));
to.setAmount(toValue.add(amount));
return true;
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6303 次 |
| 最近记录: |