关于访问和编辑全局字段的多个线程
Posted
技术标签:
【中文标题】关于访问和编辑全局字段的多个线程【英文标题】:On Multiple Threads Accessing and Editing a Global Field 【发布时间】:2017-09-29 10:57:55 【问题描述】:我正在尝试使用多线程来模拟单个银行帐户上的两个用户,提取和存入资金。我希望两个用户对单个共享变量进行操作,当然代表帐户余额。
存款或取款的动作是随机选择的 - 1 或 2(分别为存款和取款)。存款时,我希望存款动作需要 1 秒,提款动作需要 0.5 秒。在这个时间间隔内,线程必须等待另一个线程完成一个动作,然后才能提取/存款自己。
然而,我最大的问题是两个线程如何分别编辑一个共享的数字字段,即余额。我第一次尝试时,每个线程都创建了自己的 balance 实例并分别对它们执行操作。我希望每个线程的操作(提款/存款)影响全局字段“余额” - 而不是实例字段。
我目前构建的Thread类和Driver类如下。
线程创建者类:
public class BankAccountSim extends Thread
public double balance = 1000;
public String threadName;
BankAccountSim(String name)
threadName = name;
public void run()
System.out.println(threadName + "account initiated.");
while(true)
try
Random rand = new Random();
int num = rand.nextInt(2) + 1;
if(num == 1)
System.out.println(threadName + " is depositing in the bank.");
balance += 1;
System.out.println("The new balance is " + balance + " dollars" );
Thread.sleep(1000);
Thread.yield();
else
System.out.println(threadName + " is withdrawing from the bank.");
balance -= 1;
System.out.println("The new balance is " + balance + " dollars.");
Thread.sleep(500);
Thread.yield();
catch(InterruptedException e)
System.out.println("Process terminated.");
线程驱动类:
import java.util.concurrent.ThreadLocalRandom;
import java.util.Random;
public class BankAccountSimDriver
public static void main(String[] args)
Thread user1 = new BankAccountSim("user1");
Thread user2 = new BankAccountSim("user2");
user1.start();
user2.start();
【问题讨论】:
你可以将balance
设为静态,甚至可以使用AtomicInteger
与其让BankAccountSim
扩展自Thread
,不如简单地把它变成一个简单的类。创建此类的单个实例,然后将其传递给您的线程,然后线程应对其执行操作。 BankAccountSim
应该提供 synchronized
方法,withdraw
或 deposit
给帐户
如果你真的想使用这个代码,那么 Money 永远不应该是 double
我认为这个确切的场景在 Core Java Volume 1 中得到了解决。
【参考方案1】:
这主要是一个设计问题。您最好创建一个银行帐户并与客户共享,就像在现实世界中一样。
interface BankAccount
// Query the balance
BigDecimal balance();
// Make a deposit and return the new balance
BigDecimal deposit(BigDecimal amount);
// Make a withdrawal and return the new balance
BigDecimal withdraw(BigDecimal amount);
// Implements the Runnable interface for run in another thread.
class BankClient implements Runnable
private final String clientName;
private final BankAccount bankAccount;
public BankClient(String clientName, BankAccount bankAccount)
this.clientName = clientName;
this.bankAccount = bankAccount;
public void run()
// while(true) ...
class SimpleBankAccount implements BankAccount
private BigDecimal balance = BigDecimal.ZERO;
public SimpleBankAccount(BigDecimal initialBalance)
this.balance = initialBalance;
public synchronized BigDecimal balance ()
return balance;
public synchronized BigDecimal deposit(BigDecimal amount)
return balance = balance.add(amount);
public synchronized BigDecimal withdraw(BigDecimal amount)
if (balance.compareTo(amount) < 0)
throw new RuntimeException("Not enough balance.");
return balance = balance.subtract(amount);
public class SimDriver
public static void main(String[] args) throws Exception
BankAccount account = new SimpleBankAccount(BigDecimal.valueOf(1000));
Thread t1 = new Thread(new BankClient("client-1", account));
thread t2 = new Thread(new BankClient("client-2", account));
t1.start();
t2.start();
t1.join();
t2.join();
【讨论】:
嗨 Luke - 非常有帮助,但有一个问题:如何在 BankAccount 类中编写 run() 方法,以便可以调用同步的存款和取款方法?还是我错过了什么?【参考方案2】:有趣的是,您想要一个在它自己的线程中,但不要将BankAccount
视为线程,Action 是线程。关于这个设计的一个注释。我个人的经验法则是我只将volatile
用于原始类型。如果您将balance
更改为Double
或其他对象,请锁定Object
或使用AtomicReference
或其他对象。
public class BankAccount
static volatile double balance = 0;
public void deposit(double amount)
class Deposit implements Runnable
double amount;
Deposit(double d) amount = d;
public void run()
balance += amount;
new Thread(new Deposit(amount)).run();
public synchronized void withdraw(double amount)
class Withdraw implements Runnable
double amount;
public Withdraw(double d) amount = d;
public void run()
balance -= amount;
new Thread(new Withdraw(amount)).run();
//
// A little Test
//
public static void main(String[] args)
BankAccount ba = new BankAccount();
ba.deposit(34576.2);
【讨论】:
【参考方案3】:一个简单的解决方案是通过更改来平衡AtomicInteger:
public double balance = 1000;
到
public AtomicInteger balance = new AtomicInteger(1000);
但是,您需要稍微修改更新余额的方式,例如添加到其中:
balance.addAndGet(1);
【讨论】:
以上是关于关于访问和编辑全局字段的多个线程的主要内容,如果未能解决你的问题,请参考以下文章