多线程程序中出现意外结果

时间:2015-01-04 19:23:09

标签: java arrays multithreading

这个简单的程序有一个共享数组和2个线程: 第一个线程 - 显示数组中的值的总和。 第二个线程 - 从数组的一个单元格中减去200,并将200添加到另一个单元格。

我希望看到结果:1500(数组的总和),1300(如果在减法和加法之间出现显示)。

但由于某种原因,有时会出现1100和1700,我无法解释......

public class MainClass {

    public static void main(String[] args) {
        Bank bank = new Bank();
        bank.CurrentSum.start();
        bank.TransferMoney.start();
    }
}

class Bank {
    private int[] Accounts = { 100, 200, 300, 400, 500 };
    private Random rnd = new Random();

    Thread CurrentSum = new Thread("Show sum") {
        public void run() {
            for (int i = 0; i < 500; i++) {
                System.out.println(Accounts[0] + Accounts[1] + Accounts[2]
                        + Accounts[3] + Accounts[4]);
            }
        }
    };

    Thread TransferMoney = new Thread("Tranfer"){
        public void run(){
            for(int i=0; i<50000; i++)
            {
                Accounts[rnd.nextInt(5)]-=200;
                Accounts[rnd.nextInt(5)]+=200;
            }
        }
    };
}

4 个答案:

答案 0 :(得分:2)

您没有以原子或线程安全的方式更新值。这意味着有时你会看到两个比200更多的200,有时你会看到两个+200比-200。当您迭代值时,可能会看到+200值,但-200值是较早的值而您错过了,但是您再次看到另一个+200更新错过了-200更改。

在极少数情况下应该可以看到最多5 x + 200或5 x -200。

答案 1 :(得分:0)

也许是故意的,你没有原子地进行加法操作。

这意味着这一行:

System.out.println(Accounts[0] + Accounts[1] + Accounts[2]
                        + Accounts[3] + Accounts[4]);

将以多个步骤运行,其中任何一个步骤都可能在第二个线程的任何迭代期间发生。

1. Get value of Accounts[0] = a
2. Get value of Accounts[1] = b
...So on

然后在从阵列中拉出所有值之后发生添加。

您可以想象从JRE中取消引用的Accounts [0]中减去200,然后在第二个线程的另一个循环中,从Accounts [1]中删除200,随后由JRE取消引用。这可能会导致您看到的输出。

答案 2 :(得分:0)

这种情况正在发生,因为添加五个值不是原子的,并且可能会被另一个线程中发生的减量和增量中断。

这是一个可能的案例。

  • 显示主题添加Accounts[0]+Accounts[1]+Accounts[2]
  • 更新线程递减Accounts[0]并递增Accounts[3]
  • 更新线程递减Accounts[1]并递增Accounts[4]
  • 显示主题继续添加,将Accounts[3]Accounts[4]添加到已经部分评估的总和中。

在这种情况下,总和将是1900,因为在增加后你已经包含了两个值。

你应该能够计算出这样的案例,给你7002300之间的任何总和。

答案 3 :(得分:0)

正在从多个线程访问Accounts变量,其中一个线程会修改其值。为了让其他线程可靠地读取修改后的值,有必要使用&#34;内存屏障&#34;。 Java有许多提供内存障碍的方法:synchronized,volatile或其中一种Atomic类型是最常见的。

Bank类还有一些逻辑,要求在Accounts变量恢复到一致状态之前,在多个步骤中进行修改。 synchronized关键字还可用于防止在同一对象上同步的另一个代码块在第一个同步块完成之前运行。

Bank类的这种实现使用拥有Accounts变量的Bank对象的互斥锁对象来锁定对Accounts变量的所有访问。这可确保每个同步块在其他线程可以运行其自己的同步块之前完整运行。它还确保对其他线程可以看到对Accounts变量的更改:

class Bank {
    private int[] Accounts = { 100, 200, 300, 400, 500 };
    private Random rnd = new Random();

    Thread CurrentSum = new Thread("Show sum") {
        public void run() {
            for (int i = 0; i < 500; i++) {
                printAccountsTotal();
            }
        }
    };

    Thread TransferMoney = new Thread("Tranfer"){
        public void run(){
            for(int i=0; i<50000; i++)
            {
                updateAccounts();
            }
        }
    };

    synchronized void printAccountsTotal() {
        System.out.println(Accounts[0] + Accounts[1] + Accounts[2]
                + Accounts[3] + Accounts[4]);
    }

    synchronized void updateAccounts() {
        Accounts[rnd.nextInt(5)]-=200;
        Accounts[rnd.nextInt(5)]+=200;
    }
}