2011-06-12 395 views
3

我想了解一下我最近的多线程思想。这里有:避免使用状态变量副本进行阻塞?

假设我有以下(伪)类,它的run()方法在某个线程上永远徘徊。其他线程将在随机时间使用setState()更改Foo实例的状态。 run()所做的工作只涉及读取状态变量,不写入,并且在while语句的一次执行过程中(例如:绘制位图),状态不得更改。在这种情况下,拥有2个状态变量副本似乎可以防止很多潜在的阻塞(因为如果我只有一个共享状态变量副本,我将不得不同步while循环中的所有内容(使用stateLock)而外部线程可能没有机会改变状态)。代码中断后的问题。

class Foo { 
    Object stateLock = new Object(); 

    private float my1, my2, my3; 
    private float sh1, sh2, sh3; // sh stands for shared 

    public void setState(...) { 
    synchronized (stateLock) { 
     // modify sh1, sh2, or sh3 here 
    } 
    } 

    private void updateState() { 
    synchronized (stateLock) { 
     // set my1=sh1, my2=sh2, my3=sh3 
    } 
    } 

    public void run() { 
    while(true) { 
     updateState(); 
     // then do tons of stuff that uses my1,my2,my3 over and over... 
     ... 
    } 
    } 
} 

这个逻辑的任何漏洞?有没有一种“标准化”或更聪明的方式来做到这一点?如果有大量的状态变量呢?更糟糕的是,如果状态变量是不容易复制的自定义对象(例如,在Java中,自定义对象的变量是引用)?

顺便说一下,这来自我目前在Android中使用SurfaceView的工作。

回答

2

要使所有变量保持同步并避免同步,可以将变量放入不可变对象中并作为一个整体进行更新。读取状态时,请将一个状态对象保持为局部变量,并保证在您读取它时没有其他人更新它。

这是一些示例代码(未经测试等)。如果旧值不能在setState中读取,或者只能从一个线程访问,那么一个易失性字段就足够了。但在一般情况下(多线程调用setState并且新状态取决于旧状态的值),使用AtomicReference可以确保不会更新。

class Foo { 
    private final AtomicReference<State> state = new AtomicReference<State>(new State(0, 0, 0)); 

    private void setState(float x1, float x2, float x3) { 
     State current; 
     State updated; 
     do { 
      current = state.get(); 
      // modify the values 
      float sh1 = current.sh1 + x1; 
      float sh2 = current.sh2 + x2; 
      float sh3 = current.sh3 + x3; 
      updated = new State(sh1, sh2, sh3); 
     } while (!state.compareAndSet(current, updated)); 
    } 

    public void run() { 
     while (true) { 
      State snapshot = state.get(); 
      // then do tons of stuff that uses sh1, sh2, sh3 over and over... 
     } 
    } 

    private class State { 
     public final float sh1, sh2, sh3; 

     State(float sh1, float sh2, float sh3) { 
      this.sh1 = sh1; 
      this.sh2 = sh2; 
      this.sh3 = sh3; 
     } 
    } 
} 

下面是特殊情况下的示例代码,更新状态不依赖于国家的旧值:

class Foo { 
    private volatile State state = new State(0, 0, 0); 

    private void setState(float sh1, float sh2, float sh3) { 
     state = new State(sh1, sh2, sh3); 
    } 

    public void run() { 
     while (true) { 
      State snapshot = state; 
      // then do tons of stuff that uses sh1, sh2, sh3 over and over... 
     } 
    } 

    private class State { 
     public final float sh1, sh2, sh3; 

     State(float sh1, float sh2, float sh3) { 
      this.sh1 = sh1; 
      this.sh2 = sh2; 
      this.sh3 = sh3; 
     } 
    } 
} 
+0

这是打包这个过程的好方法(并且没有任何明确的同步---奖金!)。谢谢! (我碰巧在你的文章后面阅读了Java并发实践中的这一章。) – heycosmo 2011-06-13 07:05:18

+0

我还没有阅读“实践中的Java并发性”,但可能我从其他几百个地方收集了相同的信息。 :) – 2011-06-14 22:29:52

3

有修复此的简单的方法是:

private volatile float sh1, sh2, sh3; // note "volatile" 

在Java内存模型,线程被允许从其他线程的缓存值。 volatile关键字表示所有线程都必须使用相同的变量值(即全部引用变量的相同内存位置)。当与基元一起使用时,这意味着您不需要同步(尽管使用64位基元,其中浮点数为而非之一,但您可能不需要同步,这取决于您的JVM是32位还是64位)

您可能想要观察部分/不一致的更新 - 其中sh变量的一些在另一个线程读取它们时被更新。您可能希望通过更新多个sh变量“原子”来同步更新以保持一致的状态。

+0

不幸的是,我不认为这会有所帮助。删除my1,my2,my3以支持共享易失性变量,违背了在while循环迭代过程中(本地)状态不能改变的要求。虽然保证读/写不会使用volatile进行交错,但并不意味着状态变量不能被另一个线程在一次迭代中改变。 Esko的解决方案恰好来自Java实践中的并发。 – heycosmo 2011-06-13 07:01:58

+0

对不起,也许你不是故意删除my1,my2,my3。然而,在这种情况下,使sh1,sh2,sh3易失性是多余的,因为'stateLock'“雾化”了状态设置和获取。 – heycosmo 2011-06-13 07:14:38