我的导师说使用多线程来更新帐户管理系统。以下给出了系统的概念。
这是我的源代码。
帐户类
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;
}
}
ThreadExercise类
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类中的makeWithdraw()和deposit()方法,我应该删除同步并同步Account类中的withdraw()和deposit()。请给我一个清晰的想法。
感谢您的支持。
答案 0 :(得分:3)
帐户类
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();
}
}}
人类
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;
}}
ThreadExercise类
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());
}}
答案 1 :(得分:2)
考虑到设计,必须同步Account类(其中的方法)。
目前其他人的方式可能会将一个实例检索到一个帐户,并以一种非线程安全的方式使用它。在这种情况下,只需从其他地方调用Account'methods就可以了。
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;
}
}
}
答案 2 :(得分:2)
您的帐户类不是线程安全的。虽然您已同步存款和撤销ThreadExercise类的方法,可以在存款/取款锁定线程时更改基础余额。
考虑场景
线程1调用ThreadExercise.deposit它检查余额并等待。 同时Thread 2唤醒并更新余额。
因此,您的帐户余额与并发存款+取款电话并非真正同步。
您可以如下定义余额。
AtomicInteger balance = new AtomicInteger(1000);
然后撤销方法可以写成如下
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);
}
您可能需要处理故障情况。
答案 3 :(得分:2)
我不确定其他答案是否清楚。
您synchronized
类ThreadExercise
上的方法ThreadExercise
。
这意味着只有一个线程可以同时在给定的synchronize
对象上调用这些方法。
这没有任何效果,因为每个线程对象无论如何都只会在一个这样的对象上调用方法。
您需要Account
Account
类的方法,以确保一次只有一个线程在任何给定的Account
上调用一个方法。
当然,在任何真正的系统Account 1: $100
Account 2: $0
Account 1: $40
Account 2: $0
Account 1: $40
Account 2: $60
对象都会(以某种方式)序列化到某个数据库或对象存储库,你需要确保你的应用程序没有引入两个&#39; doppelganger&# 39;代表一个物理的物体。帐户。在具有多个ATM交换机的分布式系统上,这可能会很棘手。
如果您介绍平衡转移的想法,您可能需要引入进一步的同步。如果某些观察员进程看不到这一点,那就特别正确:
synchronized
其中60美元的转移被视为消失并重新出现。
那里没有业务问题。银行让数百万人从坐在上面的X拿钱,然后把它传递到Y,没有充分的理由比他们可以给客户挤奶。
我只是指出向方法添加Account 1: $100
Account 2: $60
并不是并发编程的全部答案。
我曾经看到一个组织设法执行此类转移并在中间出现错误并将帐户留在州内:
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;
}
}
从账户1到2的60美元(1000万美元IRL)到达但从未离开过!这是另一个故事......
然而,要回答这个问题:
getBal()
我挑衅地没有同步int
。一方面balance
读取和写入都是原子的,因此它总是会读取long
的一致值而不是某些“混蛋”。其中(比方说)写操作只更新了低字节。如果您将其更改为 Account 1: $100
Account 2: $60
,则JVM不再提供此保证。
然而,不同步它意味着您可以看到异常位置:
account1.withdraw(60);
account2.deposit(60);
即使你的代码是:
,也可能发生account1
这是因为同步不会引入阻塞,但也会影响内存屏障。假设一个单独的线程有account2
缓存但没有account1
没有同步,它不知道account2
是陈旧的,但是获取java.util.concurrent.atomic.AtomicInteger
的最新版本。
值得注意的是,您的课程非常简单,您可以使用addAndGet(int delta)
使用{{1}}。
但是,只要您开始添加复杂程度(例如透支限额),您就需要重新进行同步。