我在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
,它是对对象的引用(Long
和long
不同)。
您需要在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.*
中的集合
编辑: 感谢您对addAndGet
vsincrementAndGet
的更正。
您正在同步非最终值:
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;
}
}
}