Java多线程问题,关于为什么这个变量没有如期改变。

huangapple go评论138阅读模式
英文:

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 packet80
b-->money in packet80
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 remains20
b-->account remains20
a-->money in packet80
b-->money in packet80
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,&quot;account&quot;);
SafeDrawing a = new SafeDrawing(account,80,&quot;a&quot;);
SafeDrawing b = new SafeDrawing(account,80,&quot;b&quot;);
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()+&quot; comes in!&quot;);
if(account.money-drawingMoney&lt;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()+&quot;--&gt;account remains:&quot;+account.money);
System.out.println(this.getName()+&quot;--&gt;money in packet:&quot;+packetTotal);
System.out.println(Thread.currentThread().getName()+&quot; comes out!&quot;);
}
@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--&gt;account remains:-60
a--&gt;account remains:-60
a--&gt;money in packet:80
b--&gt;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--&gt;account remains:20
b--&gt;account remains:20
a--&gt;money in packet:80
b--&gt;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()+&quot;--&gt;account remains:&quot;+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().

huangapple
  • 本文由 发表于 2020年8月24日 02:01:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/63550369.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定