为什么系统应该打印给我的结果每次都会改变(java 并发)?



我在java中的并发编程中遇到了问题。请查看下面的代码。每次我运行程序时,系统应该打印给我的结果都会更改。 虽然我已经同步了向子变量添加值的操作,但结果每次都会改变。我想我在某处犯了一个错误。但我不知道在哪里。

public class Test {
public static void main(String[] args) {
final MyClass mClass = new MyClass();
int size = 10;
final CountDownLatch cdl = new CountDownLatch(size);
for(int i = 0; i < size; i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for(int number = 0; number < 100000; number++){
mClass.addToSub(number);
}
cdl.countDown();
}
});
t.start();
}
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//the result changes every time!!!!!!!!
System.out.println(mClass.getSub());
}
public static class MyClass {
private Long sub = 0L;
public long getSub() {
synchronized (sub) {
return sub;
}
}
public void addToSub(long value){
synchronized (sub) {
sub += value;
}
}
}
}

你在这里出错的不是多线程。导致此问题的是称为自动装箱的 java 功能。

您的变量sub具有类型Long,它是对对象的引用(Longlong不同)。

您需要在java中有一个要同步的对象,因此不能仅使用普通long

这里的问题是Long是不可变的,这意味着值不会改变。所以当你做sub += value你实际上是在做sub = Long.valueOf(sub.longValue() + value)女巫正在创建一个新对象。

因此,当前线程仅锁定了以前的对象,因此新线程仍然可以更改引用sub

您要做的是在不会更改的引用上进行同步,即this

public void addToSub(long value){
synchronized (this) {
sub += value;
}
}

或更简洁:

public synchronized void addToSub(long value) {
sub += value;
}

您可能应该使用long而不是Long.

编辑

正如Thomas Timbuls回答中所述,您可能希望使用AtomicLong,因为它默认为您提供线程安全,并且可能具有更好的性能(因为线程不需要相互等待)。

addToSub中,您正在更改synchronize的值。实际上,这意味着根本没有同步。

要么在this同步,要么更好的是,使用 AtomicLong 并避免您的问题以及同步开销(线程争用):

public static class MyClass {
private AtomicLong sub = new AtomicLong();
public long getSub() {
return sub.get();
}
public void addToSub(long value){
sub.addAndGet(value);
}
}

Atomic* 类是专门为此类用例设计的,其中单个变量由多个线程更新,并且synchronize可能导致严重的线程争用。如果您正在处理集合,请查看java.util.concurrent.*中的集合

编辑: 感谢您对addAndGetvsincrementAndGet的更正。

您正在同步非最终值:

synchronized (sub) {

这意味着一旦您将其更改为其他值:

sub += value;

任何尚未在同步块上等待的内容都可以继续,因为没有任何内容将监视器用于此新值。

改为在this上同步(或其他一些不变的值):

synchronized (this) {

sub 是一个对象(Long),更改为 long 并为同步添加一个私有对象。然后它就会起作用。

public static class MyClass {
private Object locker = new Object();
private long sub = 0L;
public long getSub() {
synchronized (locker) {
return sub;
}
}
public void addToSub(long value){
synchronized (locker) {
sub += value;
}
}
}

最新更新