简体   繁体   中英

Multithreading in Java: how to transfer funds between accounts correctly?

Trying to make a transfer between conditional accounts in a multi-threaded environment, how true is my approach and what am I wrong about?

As planned, I create a new Thread in main, initialize a new class of type Transfer in this new Thread, after I take the data of 2 accounts from the database, which I determine randomly, the amount I want to transfer randomly. I translate and write the changes back to the database. The sum of all funds in the accounts must remain correct. That is, if we have 50 accounts with 1000 money each (50,000 for all together), after all transactions there should still be no more and no more than 50,000.

The approach described below works if the threads are no more than 5-10 and the threads sleep more than 500ms. Maybe I have the wrong approach to solving the problem. I tried using tryLock, it turns out the same. In total, the money then goes to plus, then to minus.

Plus, I'm trying to do this on Hibernate.
What i have:
1. Class some bank account - Account (@Entity)
2. Class TransferThread extends Thread
3. Class Transfer - work with transfers 4. Class Main

Now it looks like this: Class Account (set/get (I don't write they here, but they are there)):

public class Account {
  private int id;
  private int money;

  public Account() {
  }
  public void widrawal(int sum) {
    money += sum;
  }
  public void send(int sum) {
    money -= sum;
  }
}

Class TransferThread (according to the idea, fulfills the transaction and falls asleep for a random time (10 times)):

public class TransferThread extends Thread {
  Transfer transfer= new Transfer();
  AtomicInteger atomicInteger = new AtomicInteger();

  public void run() {
    atomicInteger.set(10);
    while (atomicInteger.get() > 0) {
        //thread sleep for random time
        int a = (int) (Math.random() * (500 - 100)) + 100;
        try {
            transfer.transaction();
            atomicInteger.getAndDecrement();
            Thread.sleep(a);
        } catch (InterruptedException e) {
      }
    }
  }
}

Class Transfer:

public class Transfer {




public void transaction() throws InterruptedException {


    int sumSpis = (int) (Math.random() * 100) + 10;
    int ranAccount1 = (int) (Math.random() * 50) + 1;
    int ranAccount2 = (int) (Math.random() * 50) + 1;
    Account a1 = null;
    Account a2 = null;
    a1 = HibernateSessionFactoryUtil.getSessionFactory().openSession().get(Account.class, ranAccount1);
    a2 = HibernateSessionFactoryUtil.getSessionFactory().openSession().get(Account.class, ranAccount2);

    int fromId = a1.getId();
    int toId = a2.getId();

    if (fromId < toId) {
        synchronized (a1) {
            synchronized (a2) {
                transfer(a1, a2, sumSpis);

            }
        }
    } else {
        synchronized (a2) {
            synchronized (a1) {
                transfer(a1, a2, sumSpis);
            }
        }
    }

    System.out.println("amount: " + sumSpis);


}

public void transfer(Account account1, Account account2, int sum) {
    if (account1.getId() == account2.getId()) {
        System.out.println("same IDs");
        return;
    }

    if (account1.getMoney() < sum) {
        System.out.println("not enough account funds");
        return;
    }

    account1.widrawal(sum);
    account2.send(sum);

    Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
    Transaction tx = session.beginTransaction();
    session.update(account1);
    session.update(account2);
    tx.commit();
    session.close();
}
}

The order of actions should be the following: get session, start database transaction, get data for acc1 from database, get data for acc2 from database, transfer money, save data for acc1 into database, save data for acc2 into database, commit database transaction.

In your code you get data for accounts from database and save new data in separate sessions s, which means that transactions are also separate. And it is possible that between get from database and save into database I one thread another thread would have been already updated data for those accounts and you are overwriting that data with stale data.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM