关于访问和编辑全局字段的多个线程

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 方法,withdrawdeposit 给帐户 如果你真的想使用这个代码,那么 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);

【讨论】:

以上是关于关于访问和编辑全局字段的多个线程的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal和异步

多线程访问独立空间全局变量

多线程访问独立空间全局变量

多线程线程同步

Java内存模型-关于多线程(转)

访问全局数组会导致分段错误