如何在简单的现金存取程序中处理多线程

How to handle multithreading in simple cash deposit withdraw program

我的导师说要使用多线程来更新帐户管理系统。下面给出了该系统的粗略概念。

这是我的源代码。

帐号class

public class Account {
    int balance= 1000;

    public int getBal(){
        return balance;
    }

    public void withdraw(int bal){
        balance= balance-bal;
    }

    public void deposit(int bal){
        balance= balance+bal;
    }
}

线程练习class

public class ThreadExercise implements Runnable{

    Account acc = new Account();

    public static void main(String[] args) {
        ThreadExercise ts = new ThreadExercise();
        Thread t1 = new Thread(ts, "person 1");
        Thread t2 = new Thread(ts, "person 2");
        Thread t3 = new Thread(ts, "person 3");
        t1.start();
        t2.start();
        t3.start();
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            makeWithdraw(100);
            if (acc.getBal() < 0) {
                System.out.println("account is overdrawn!");
            }
            deposit(200);
        }
    }


    private synchronized void makeWithdraw(int bal){
        if (acc.getBal()>=bal) {
            System.out.println(Thread.currentThread().getName()+" "+ "is try to withdraw");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            acc.withdraw(bal);
            System.out.println(Thread.currentThread().getName()+" "+ "is complete the withdraw");
        }else{        
            System.out.println(Thread.currentThread().getName()+ " "+"doesn't have enough money for withdraw ");
        }
    }

    private synchronized void deposit(int bal){
        if (bal>0) {
            System.out.println(Thread.currentThread().getName()+" "+ " is try to deposit");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            acc.deposit(bal);
            System.out.println(Thread.currentThread().getName()+" "+ "is complete the deposit");
        }else{        
            System.out.println(Thread.currentThread().getName()+ " "+"doesn't have enough money for deposit");
        }
    }
}

代码运行良好。但我真的认为有些东西缺少这段代码。你能帮我找出那个错误吗?


同步 ThreadExercise class 中的 makeWithdraw() 和 deposit() 方法是否不够,我是否应该删除同步并同步 Account class 中的 withdraw() 和 deposit()。请给我一个清晰的思路。
感谢您的支持。

考虑到帐户 class 的设计必须同步(其中的方法)。

目前的方式是其他人可能会检索一个帐户的实例并以非线程安全的方式使用它。在这种情况下,只需从其他地方调用 Account 的方法即可。

public class Account {
  private final Object lock = new Object();
  // Must be private to be thread-safe!
  private int balance= 1000;

  public int getBal(){
    return balance;
  }

  public synchronized void withdraw(int bal){
    synchronized (lock) {
      balance= balance-bal;
    }
  }

  public synchronized void deposit(int bal){
    synchronized (lock) {
      balance= balance+bal;
    }
  }
}

您的帐户 class 不是线程安全的。虽然您已经同步了 ThreadExercise class 的 Deposit 和 withdraw 方法,但在 deposit/withdraw 锁定线程时可以更改基础余额。

考虑场景

线程 1 调用 ThreadExercise.deposit 它检查余额并等待。 同时线程 2 唤醒并更新余额。

所以你的账户余额并没有真正与并发存款 + 取款调用同步。

您可以如下定义余额。

 AtomicInteger balance = new AtomicInteger(1000);

那么withdraw方法可以这样写

public boolean withdraw (int amtToWithdraw, int existingBalance){
    return balance.compareAndSet(existingBalance,existingBalance-amtToWithdraw); 
}

public void deposit(int amtToDeposit, int existingBalance){
    return balance.compareAndSet(existingBalance,existingBalance+amtToDeposit);
}

您可能需要处理故障情况。

我不确定其他答案是否清楚。

您已经 synchronized ThreadExercise class 上的方法。 这意味着一次只有一个线程可以在给定的 ThreadExercise 对象上调用这些方法。 这没有任何效果,因为每个线程对象无论如何都只会调用一个这样的对象上的方法。

您需要 synchronize Account class 的方法,以确保一次只有一个线程在任何给定的 Account 上调用一个方法。

当然在任何真实系统中 Account 对象会(以某种方式)序列化到某个数据库或对象存储,您需要确保您的应用程序没有引入两个 'doppelganger' 对象代表一个 'physical' 帐户。这在具有多个 ATM 交换机的分布式系统上可能很棘手。

如果您引入余额转移的想法,您可能需要引入进一步的同步。如果某些观察者进程无法接受,则尤其如此:

Account 1: 0
Account 2: [=10=]

Account 1: 
Account 2: [=10=]

Account 1: 
Account 2: 

其中一笔 60 美元的转账消失又重新出现。 那里没有业务问题。银行从 X 那里拿钱赚了数百万美元,然后无缘无故地把它转给 Y,因为他们可以榨取客户的钱。 我只是想指出,向方法中添加 synchronized 并不是并发编程的全部答案。

我曾经看到过一个组织成功执行了这样的转账,中间出现错误,导致账户处于以下状态:

Account 1: 0
Account 2: 

从帐户 1 到帐户 2 的 60 美元(1000 万美元 IRL)到达但从未离开!那是另一个故事...

然而要回答这个问题:

public class Account {
    int balance= 1000;

    public int getBal(){
        return balance;
    }

    public synchronized void withdraw(int bal){
        balance= balance-bal;
    }

    public synchronized void deposit(int bal){
        balance= balance+bal;
    }
}

我挑衅地不同步getBal()。一方面,int 读取和写入是原子的,因此它将始终读取 balance 的一致值,而不是某些 'bastard',其中(比如说)写入操作仅更新了低字节。如果您将其更改为 long,则 JVM 不再提供该保证。

然而不同步意味着你可以看到异常位置:

  Account 1: 0
  Account 2: 

即使您的代码是:

也可能发生
account1.withdraw(60);
account2.deposit(60);

那是因为同步不仅会引入阻塞,还会影响内存屏障。假设一个单独的线程有 account1 缓存但没有 account2 没有同步它不会知道 account1 是陈旧的但获取 account2.[=33= 的最新版本]

值得一提的是,您的 class 非常简单,您可以使用 java.util.concurrent.atomic.AtomicIntegeraddAndGet(int delta) 来摆脱困境。 但是,一旦您开始添加复杂性(例如透支限制),您就需要返回同步。

帐号class

public class Account {
public static Account account;
private static int balance = 1000;
private static Person person;

private Account() {
}

public static Account getAccount(Person p) {
    if (account == null) {
        account = new Account();
    }
    Account.person = p;
    return account;
}

public static int getBal() {
    return balance;
}

public synchronized void withdraw(int bal) {
    try {

        if (balance >= bal) {
            System.out.println(person.getName() + " " + "is try to withdraw");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            balance = balance - bal;
            System.out.println(person.getName() + " " + "is complete the withdraw");
        } else {
            System.out.println(person.getName() + " " + "doesn't have enough money for withdraw ");
        }
        System.out.println(person.getName() + " " + " withdraw Rs." + balance);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public synchronized void deposit(int bal) {
    try {
        if (bal > 0) {
            System.out.println(person.getName() + " " + " is try to deposit");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            balance = balance + bal;
            System.out.println(person.getName() + " " + "is complete the deposit");
        } else {
            System.out.println(person.getName() + " " + "doesn't have enough money for deposit");
        }
        System.out.println(person.getName() + " " + " deposit Rs." + balance);
    } catch (Exception e) {
        e.printStackTrace();
    }
}}

人class

public class Person {
private String name;

public Person(String name) {
    this.name = name;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

@Override
public String toString() {
    return name;
}}

线程练习class

public class ThreadExercise extends Thread implements Runnable {

private Person person;

public ThreadExercise(Person p) {
    this.person = p;
}

public static void main(String[] args) {

    ThreadExercise ts1 = new ThreadExercise(new Person("person 1"));
    ts1.start();
    ThreadExercise ts2 = new ThreadExercise(new Person("person 2"));
    ts2.start();
    ThreadExercise ts3 = new ThreadExercise(new Person("person 3"));
    ts3.start();

}

@Override
public void run() {
    for (int i = 0; i < 3; i++) {
        try {
            Account acc = Account.getAccount(person);
            acc.withdraw(100);
            try {
                Thread.sleep(200);
            } catch (InterruptedException ex) {
                Logger.getLogger(ThreadExercise.class.getName()).log(Level.SEVERE, null, ex);
            }
            if (acc.getBal() < 0) {
                System.out.println("account is overdrawn!");
            }
            acc.deposit(200);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    System.out.println("Final Acc balance is Rs." + Account.getBal());
}}