2016-04-30 34 views
3

我想运行多个线程。我清楚地让比赛条件,并能够解决这个问题如下:即使在同步后竞态条件

final Data data = new Data(); 
for (int i = 0; i < numberOfThreads; i++) { 
    final Thread thread = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      //using this sync block to stop the race condition 
      synchronized (data){ 
       final int value = data.getValue(); 
       data.setValue(value + 1); 
      } 
     } 
    }); 
    thread.start(); 
} 

但我不希望这个块上同步,而是要在数据类来处理它。所以我删除了上面的同步块,而是按如下方式同步了Data类中的get和set方法,但这仍然会导致竞争条件。为什么这个问题即使我已经同步了他们?

public class Data { 

    private int value; 

    public synchronized int getValue(){ 
     return this.value; 
    } 

    public synchronized void setValue(int num){ 
     this.value = num; 
    } 
} 
+0

因为没有额外的同步块,多个线程可以同时调用getValue(),将其加1,然后再写入相同的值,尽管它们应该增加了getValue()被调用的次数。 – markspace

+0

竞争条件究竟如何体现? 此外,如果意图是让线程安全增量,为什么不使用[AtomicInteger](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicInteger。 html)? –

+0

这是双重检查锁定的变体(https://en.wikipedia.org/wiki/Double-checked_locking) - 如答案所示,您正在获取并单独设置。 – stdunbar

回答

2

添加​​个别方法类似于做这样的事情

final Data data = new Data(); 
for (int i = 0; i < numberOfThreads; i++) { 
    final Thread thread = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      synchronized (data){ 
       final int value = data.getValue(); 
      } 
      synchronized (data){ 
       data.setValue(value + 1); 
      } 
     } 
    }); 
    thread.start(); 
} 

当一个线程可以很清楚卡住查询和设置之间得到。为了解决这个问题,您可能需要一个新的同步方法添加到Data类完成的任务value + 1,或包裹两条线在​​块,当你在你的代码已经完成。

4

因为你没有。您同步的任何一方,所以两个线程不能同时执行的方法,但一个线程可以执行getValue()然后完成getValue()和进入setValue()之前,另一个线程获取其转并调用getValue()这是完全合法的,你的竞争条件。

顺便说一句。以防万一Data将是你的全班,AtomicInteger将是相同的,但做得很好。你在那里。 G。有一个incrementAndGet()方法,它执行之间的读写操作,一个同步块,这是您的案例中的要点。

1

首先,在你的Datavalue应该是volatile

关于你提到的问题;你不能像你那样改变代码。 由于你的方式改成了会出现以下情况:

1)valueData0

2)Thread 0读取value(读取0

3)Thread 1读取value(读取0

4)Thread 1增量value并写入新的价值Data.value(写入1

5)Thread 0增量value和新的值写入Data.value(写入1

这里的问题是,在步骤5)1是因为Thread 0写不知道Thread 1已经增加value因为Thread 0阅读value

+0

如果多个线程仅与同步块内的此字段交互,则volatile的使用是无关紧要的。 – KookieMonster

+0

@KookieMonster 'volatile'在Java中有2个函数; 1)确保不读取/写入部分值 2)强制值不存储在线程的本地缓存/内存中 在这种情况下,您显然不必担心情况1(从技术上讲,你永远不用担心这个'int's) 但是,除非你使用'volatile',否则如果int由另一个线程更新,不同的线程可能不会看到最近的值线程有其自己的缓存 – Tmr

+0

一般而言,这是正确的。但是当使用'synchronized'时'volatile'不需要'更新'值,因为这些值已经在'synchronized'块的末尾'更新'了(这并不是说'volatile'和'synchronized'使其他冗余,你在这种特殊情况下不需要'volatile')。 – KookieMonster

0

您在方法级别添加同步。所以一个线程可能会调用getValue(),另一个可能会调用setValue()。您可以完全删除同步并将私有“值”成员的类型更改为AutomicInteger。然后在该类中使用线程安全的方法。

+0

无法在方法上同步。你的代码唯一可以同步的是一个对象。同步实例方法在'this'对象上同步,同步类方法在类对象上同步。 –

+0

谢谢。我纠正了我的帖子。 –