英文:
Java multithreading problem about why this variable didn't change as hoped
问题
Sample code is as this:
package SynTest;
public class Test01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Account account = new Account(100,"account");
SafeDrawing a = new SafeDrawing(account,80,"a");
SafeDrawing b = new SafeDrawing(account,80,"b");
a.start();
b.start();
}
}
class Account{
int money;
String name;
public Account(int money,String name) {
this.money = money;
this.name = name;
}
}
class SafeDrawing extends Thread{
volatile Account account;
int drawingMoney;
int packetTotal;
public SafeDrawing(Account account,int drawingMoney,String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
public void test(){
System.out.println(Thread.currentThread().getName()+" comes in!");
if(account.money-drawingMoney<0) {
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
account.money -= drawingMoney;
packetTotal += drawingMoney;
System.out.println(this.getName()+"-->account remains:"+account.money);
System.out.println(this.getName()+"-->money in packet:"+packetTotal);
System.out.println(Thread.currentThread().getName()+" comes out!");
}
@Override
public void run() {
test();
}
}
Two threads are startd in main, and each of them owns a same object named account. And each thread reduces the money property of account, which should be bigger than 0;
Now the method test() is obviously unsafe, so the account.money can be less than 0, just like:
b comes in!
a comes in!
b-->account remains:-60
a-->account remains:-60
a-->money in packet:80
b-->money in packet:80
a comes out!
b comes out!
But when I kept running this code once and once again, I found an output like this:
a comes in!
b comes in!
a-->account remains:20
b-->account remains:20
a-->money in packet:80
b-->money in packet:80
a comes out!
b comes out!
This is weird because since these two threads both have run to the line System.out.println(this.getName()+"-->account remains:"+account.money);
, the code account.money -= drawingMoney;
must have been executed twice too, and why the remaining money is 20 rather than -60? Even if the happen-beefore is considered, since the account is defined as volatile, it's still impossible to be 20 rather than -60. I just can't figure it out, and thanks for any idea.
英文:
Sample code is as this:
package SynTest;
public class Test01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Account account = new Account(100,"account");
SafeDrawing a = new SafeDrawing(account,80,"a");
SafeDrawing b = new SafeDrawing(account,80,"b");
a.start();
b.start();
}
}
class Account{
int money;
String name;
public Account(int money,String name) {
this.money = money;
this.name = name;
}
}
class SafeDrawing extends Thread{
volatile Account account;
int drawingMoney;
int packetTotal;
public SafeDrawing(Account account,int drawingMoney,String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
public void test(){
System.out.println(Thread.currentThread().getName()+" comes in!");
if(account.money-drawingMoney<0) {
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
account.money -= drawingMoney;
packetTotal += drawingMoney;
System.out.println(this.getName()+"-->account remains:"+account.money);
System.out.println(this.getName()+"-->money in packet:"+packetTotal);
System.out.println(Thread.currentThread().getName()+" comes out!");
}
@Override
public void run() {
test();
}
}
Two threads are startd in main, and each of them owns a same object named account. And each thread reduces the money property of account, which should be bigger than 0;
Now the method test() is obviously unsafe, so the account.money can be less than 0, just like:
b comes in!
a comes in!
b-->account remains:-60
a-->account remains:-60
a-->money in packet:80
b-->money in packet:80
a comes out!
b comes out!
But when I kept running this code once and once again, I found an output like this:
a comes in!
b comes in!
a-->account remains:20
b-->account remains:20
a-->money in packet:80
b-->money in packet:80
a comes out!
b comes out!
This is weird because since these two threads both have run to the line System.out.println(this.getName()+"-->account remains:"+account.money);
, the code account.money -= drawingMoney;
must have been executed twice too, and why the remaining money is 20 rather than -60? Even if the happen-beefore is considered, since the account is defined as volatile, it's still impossible to be 20 rather than -60. I just cant't figure it out, and thanks for any idea.
答案1
得分: 1
解释你看到的输出,两个线程都看到了account.money
相同的值(100),从中减去了相同的值(80),并将相同的值写回(20)。在没有防止并发问题的保护措施的情况下,两个线程完全执行了相同的操作。
在SafeDrawing
中声明account
属性并不会使Account
中的money
属性变为volatile。即使这样做了,volatile在这种情况下也不起作用。
这段代码:
account.money -= drawingMoney;
即使account.money
是volatile的,也不安全。有关详细信息,请参阅这个问题和答案:
https://stackoverflow.com/questions/7805192/is-a-volatile-int-in-java-thread-safe
英文:
To explain the output you saw, both threads saw the same value for account.money
(100), subtracted the same value (80) from it, and wrote the same value back to it (20). There was no protection against concurrency issues, so both threads did exactly the same thing.
Declaring the account
property of SafeDrawing
does not make the money
property of Account
volatile. Even if it did, volatile does not work in this case.
This code:
account.money -= drawingMoney;
Would not be safe even if account.money
was volatile. See this question and answer for details:
https://stackoverflow.com/questions/7805192/is-a-volatile-int-in-java-thread-safe
答案2
得分: -1
尝试使用 AtomicInteger
AtomicInteger 类为您提供了一个可以原子地读取和写入的 int 变量,还包含高级原子操作,比如 compareAndSet()。
英文:
Try using AtomicInteger
The AtomicInteger class provides you with a int variable which can be read and written atomically, and which also contains advanced atomic operations like compareAndSet().
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论