Java同步:在帐户对之间primefaces地转移资金?

如何使钱从一个账户移动到另一个primefaces? 对于:

public class Account { public Account(BigDecimal initialAmount) {...} public BigDecimal getAmount() {...} public void setAmount(BigDecimal amount) {...} } 

我期望伪代码:

 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; } 

在multithreading环境下安全更新账户,我看到危险情况如下:

 acc1 --> acc2 || acc2 --> acc1 acc1 --> acc2 || acc2 --> acc3 || acc3 --> acc1 ... 

最简单的解决方法是在共享对象上进行阻塞,但对于像下面这样的情况来说效率会很低:

 acc1 --> acc2 || acc3 --> acc4 and acc1 != acc3 and acc2 != acc4 

我期望独立的动作是平行进行的,依次依赖。

更新似乎build议的解决scheme:

 synchronize (acc1) { synchronize (acc2) { .... } } 

导致死锁,因为2锁获得顺序…

更新2 到底什么意思是“在multithreading环境中安全地更新账户”呢? 是唯一的担心,这些帐户不会最终有负资金或有其他一些问题?

如果acc1(2); acc2(3) acc1(2); acc2(3)acc1 --1--> acc2acc2 --2--> acc1我期望一致性: (acc1, acc2)具有(3, 2)值,而不是(4, 2)(3, 4)如果您在同时执行期间获得中间账户值。

你同时期待多less并发交易? 1000-10000 – 所以locking共享对象效率不高。

一个简单的解决scheme可能是为每个帐户使用一个锁,但是为了避免死锁,您必须始终以相同的顺序获取锁。 所以,你可以有一个最终的账户ID,并获得一个较lessID的账户的锁:

 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); } } } 

一个办法是做一个事务日志。 在转移资金之前,您需要在每个账户的交易日志中写下您打算做的事情。 日志应该包含:进出账户的金额,以及日志对之间共享的锁。

最初锁应该处于阻塞状态。 您创build了一个日志对,其中一个的数量是X,另一个数量是-X,并且共享一个锁。 然后将日志条目发送到相应帐户的收件箱中,从中取出钱的帐户应保留该数额。 一旦你确认他们安全的交付,然后解锁。 锁释放的那一刻,如果没有回报,那么就是一个点。 账户然后应该自己解决。

如果任何一方希望在释放锁之前随时通过交易,则只需删除日志并将预留金额返回到主余额。

这种方法可能有点沉重,但它也可以在分布式情况下工作,其中帐户实际上在不同的机器中,并且收件箱实际上将不得不被持久化,以确保如果任何机器崩溃/丢失,金钱不会丢失意外离线。 其通用技术被称为两相locking。

我build议创build一个方法Account.withdraw(金额),如果资金不足,会抛出exception。 这个方法需要在账户本身上同步。

编辑:

还需要有一个在接收帐户实例上同步的Account.deposit(金额)方法。

基本上这会导致第一个账户被locking,同时在收款的同时收回另一个locking的收款账户。 所以两个锁,但不是在同一时间。

代码示例:假设退出/存款是同步的,返回布尔成功状态而不是抛出exception。

 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; } 

您可以创build一个额外的Account T ,仅用于转账。 所以,如果你想从A转移到B你实际上从A转移到T ,然后从T转移到B 对于这些转账中的每一个,您只能根据参与转账的账户lockingAB 由于您使用的是相同types的传输,因此最终只需要额外的代码,因此维护成本较低。

为了减less额外账户的数量,你可以把它们放在一个池中。 如果你有一个正在处理传输的线程池,那么你可以为每个线程分配它自己的额外帐户。 因此,您不需要经常向池中请求和释放这些额外帐户。

一种方法是使用在多个锁上操作的locking/解锁方法的“条纹锁”。 帐户使用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); } } 

不要使用内置的同步,使用Lock对象。 使用tryLock()同时获得对两个帐户的排他locking。 如果其中一个失败,则释放两个锁并等待一段时间,然后重试。

正如你所提到的那样,一次只能有1000-10000个并发事务,而不是你可以存储某些事务正在进行的账户,并处理并发

一个解决scheme是允许系统只创build一个微粒账户id的对象,意味着如果你想在账户“123”和“456”之间进行交易,那么你的线程将创build账户对象,并且在账户类的构造函数中,我们将检查是否有任何其他帐户对象是否有微粒帐户ID,如果其他帐户对象有相同的帐户ID意味着某些事务正在与微粒帐户ID进行,所以你必须等待获得帐户对象。

所以我们可以做“123”和“456”之间的交易,同时我们可以做“abc”和“xyz”之间的交易,但是如果同时其他一些线程会尝试创build账户“123”说,请稍候

以供参考,你可以看到下面的代码

请注意 :

  1. 不要原谅通过调用lockHolder类的freeAccount(BigDecimal accId)从锁地图中删除您的帐户ID

  2. 我已经使用HasMap的列表,因为当你随机地从中删除元素时(或者你经常更新它),列表将不是一个好的select,

     package test; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; public class T { public static void main(String[] args) { Account ac, ac2; try { ac = new Account(new BigDecimal("123")); } catch (Exception e) { e.printStackTrace(); } try { ac2 = new Account(new BigDecimal("123")); } catch (Exception e) { System.out.println("Please Wait"); } } } class Account { public Account(BigDecimal accId) throws Exception { if (LockHolder.isLocked(accId)) { throw new Exception(); } else { LockHolder.setLock(accId); } } } class LockHolder { public static Map<BigDecimal, Integer> locks = new HashMap<BigDecimal, Integer>(); public synchronized static boolean isLocked(BigDecimal accId) { return LockHolder.locks.containsKey(accId); } public synchronized static void setLock(BigDecimal accId) { LockHolder.locks.put(accId , 1); } public synchronized static void freeAccount(BigDecimal accId) { LockHolder.locks.remove(accId); } } 

如前所述,您应该locking两个帐户,始终以相同的顺序。 但是,关键部分是确保整个虚拟机实例具有较高的粒度和独特性。 这可以使用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; } } } 

即使线程可能会被任意地取消,一种仍将保持健壮的方法是让每个账户都维护一个请求或发布的交易清单。 要请求从一个帐户转移到另一个帐户,请创build一个定义请求的交易对象,并将其添加到源帐户的请求队列中。 如果该帐户可以执行该事务,则应将其移至已发送事务的列表中,并将其添加到目标的请求队列中。 使用AtomicReference ,可以确保从交易进入第一个账户的队列的那一刻起,系统的状态总是在等待,完成或中止事务,即使一些或全部线程是得到迂回,检查交易清单将有可能确定哪些钱属于哪里。

相比之下,当使用locking时,意外延迟一个线程的事件可以任意地阻止许多其他线程的执行,并且如果在locking的情况下线程被中断,可能不可能确定之前已经完成或未完成的事情。

感谢所有关心的问题。

我在https://www.securecoding.cert.org/confluence/display/java/LCK07-J.+Avoid+deadlock+by+requesting+and+releasing+locks+in+the+same+order发现了几个解决scheme

当一个链接的答案被删除这里的基本代码,帮助任何人当cert.org下降。 件很长,所以我没有包括任何优点/缺点。

私人静态最终locking对象

 final class BankAccount { private double balanceAmount; // Total amount in bank account private static final Object lock = new Object(); BankAccount(double balance) { this.balanceAmount = balance; } // Deposits the amount from this object instance // to BankAccount instance argument ba private void depositAmount(BankAccount ba, double amount) { synchronized (lock) { if (amount > balanceAmount) { throw new IllegalArgumentException( "Transfer cannot be completed"); } ba.balanceAmount += amount; this.balanceAmount -= amount; } } public static void initiateTransfer(final BankAccount first, final BankAccount second, final double amount) { Thread transfer = new Thread(new Runnable() { @Override public void run() { first.depositAmount(second, amount); } }); transfer.start(); } } 

有序locking

 final class BankAccount implements Comparable<BankAccount> { private double balanceAmount; // Total amount in bank account private final Object lock; private final long id; // Unique for each BankAccount private static long NextID = 0; // Next unused ID BankAccount(double balance) { this.balanceAmount = balance; this.lock = new Object(); this.id = this.NextID++; } @Override public int compareTo(BankAccount ba) { return (this.id > ba.id) ? 1 : (this.id < ba.id) ? -1 : 0; } // Deposits the amount from this object instance // to BankAccount instance argument ba public void depositAmount(BankAccount ba, double amount) { BankAccount former, latter; if (compareTo(ba) < 0) { former = this; latter = ba; } else { former = ba; latter = this; } synchronized (former) { synchronized (latter) { if (amount > balanceAmount) { throw new IllegalArgumentException( "Transfer cannot be completed"); } ba.balanceAmount += amount; this.balanceAmount -= amount; } } } public static void initiateTransfer(final BankAccount first, final BankAccount second, final double amount) { Thread transfer = new Thread(new Runnable() { @Override public void run() { first.depositAmount(second, amount); } }); transfer.start(); } } 

兼容解决scheme(ReentrantLock)

 final class BankAccount { private double balanceAmount; // Total amount in bank account private final Lock lock = new ReentrantLock(); private final Random number = new Random(123L); BankAccount(double balance) { this.balanceAmount = balance; } // Deposits amount from this object instance // to BankAccount instance argument ba private void depositAmount(BankAccount ba, double amount) throws InterruptedException { while (true) { if (this.lock.tryLock()) { try { if (ba.lock.tryLock()) { try { if (amount > balanceAmount) { throw new IllegalArgumentException( "Transfer cannot be completed"); } ba.balanceAmount += amount; this.balanceAmount -= amount; break; } finally { ba.lock.unlock(); } } } finally { this.lock.unlock(); } } int n = number.nextInt(1000); int TIME = 1000 + n; // 1 second + random delay to prevent livelock Thread.sleep(TIME); } } public static void initiateTransfer(final BankAccount first, final BankAccount second, final double amount) { Thread transfer = new Thread(new Runnable() { public void run() { try { first.depositAmount(second, amount); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // Reset interrupted status } } }); transfer.start(); } }