Java 多线程程序中的意外结果
此简单程序有一个共享阵列和2个线程: 第一个线程-显示数组中的值之和。 第二个线程-从数组的一个单元格中减去200,然后将200加到另一个单元格中 我希望看到结果:1500(数组的总和),1300(如果显示在减法和加法之间) 但由于某些原因,有时会出现1100和1700,我无法解释Java 多线程程序中的意外结果,java,arrays,multithreading,Java,Arrays,Multithreading,此简单程序有一个共享阵列和2个线程: 第一个线程-显示数组中的值之和。 第二个线程-从数组的一个单元格中减去200,然后将200加到另一个单元格中 我希望看到结果:1500(数组的总和),1300(如果显示在减法和加法之间) 但由于某些原因,有时会出现1100和1700,我无法解释 public class MainClass { public static void main(String[] args) { Bank bank = new Bank();
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;
}
}
};
}
public类MainClass{
公共静态void main(字符串[]args){
银行=新银行();
bank.CurrentSum.start();
bank.TransferMoney.start();
}
}
班级银行{
私人int[]账户={100200300400500};
私有随机rnd=新随机();
线程CurrentSum=新线程(“Show sum”){
公开募捐{
对于(int i=0;i<500;i++){
System.out.println(帐户[0]+帐户[1]+帐户[2]
+账户[3]+账户[4]);
}
}
};
线程转移钱=新线程(“转移”){
公开募捐{
对于(int i=0;i您没有以原子或线程安全的方式更新值。这意味着有时您会看到两个大于+200的+200,有时您会看到两个大于-200的+200。当您迭代这些值时,可能会看到一个+200值,但-200值是较早的值,您会错过它,但您会看到另一个+200更新再次丢失g-200的变化
在极少数情况下,最多可以看到5 x+200或5 x-200。可能是故意的,您没有原子化地执行加法操作
这意味着这一行:
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
然后,在从数组中提取所有值之后进行加法
您可以想象从帐户[0]中减去200,然后在第二个线程的另一个循环中,从帐户[1]中删除200,它随后被JRE取消引用。这可能会导致您看到的输出出现错误。这是因为五个值的相加不是原子的,并且可能会被另一个线程中发生的递减和递增中断
这里有一个可能的例子
- 显示线程添加
帐户[0]+帐户[1]+帐户[2]
- 更新线程递减
帐户[0]
并递增帐户[3]
- 更新线程递减
帐户[1]
并递增帐户[4]
- 显示线程继续添加,将
Accounts[3]
和Accounts[4]
添加到已部分计算的总和中
在本例中,总和将为1900,因为您在两个值递增后包含了它们
您应该能够计算出这样的情况,为您提供介于700
和2300
之间的任何值的总和,Accounts变量正在从多个线程访问,其中一个线程修改其值。为了让另一个线程可靠地读取修改后的值,必须使用“内存屏障”.Java有多种方式提供内存屏障:同步、易失或原子类型之一是最常见的
Bank类还有一些逻辑,需要在Accounts变量返回一致状态之前分多个步骤进行修改。synchronized关键字还可用于防止在同一对象上同步的另一个代码块运行,直到第一个同步块完成
Bank类的此实现使用拥有Accounts变量的Bank对象的mutex lock对象锁定对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;
}
}
类库{
私人int[]账户={100200300400500};
私有随机rnd=新随机();
线程CurrentSum=新线程(“Show sum”){
公开募捐{
对于(int i=0;i<500;i++){
printcountstotal();
}
}
};
线程转移钱=新线程(“转移”){
公开募捐{
对于(int i=0;iThanks!但是现在,基于这种理解,为什么我不能得到我想要的结果呢?也许在添加帐户[0]之前,它有很多增量,然后在添加帐户[1]之前它有很多增量,等等。你为什么说极端结果是有界的?是的,你是对的。我又考虑过了,我不认为结果是有界的。只是更极端的结果越来越不可能了-我的意思是,你应该经常得到1500
,然后是1300
不太经常,然后1700
比那少很多,以此类推。