避免使用状态变量副本进行阻止?

问题描述

| 我想对我最近的多线程想法有所了解。开始: 假设我有以下(伪)类,其class0ѭ方法正在某个线程上永久消失。其他线程将在随机时间使用
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,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,my3 over and over...
      ...
    }
  }
}
这个逻辑有漏洞吗?是否有“标准化”或更聪明的方式来做到这一点?如果有很多状态变量怎么办?更糟糕的是,如果状态变量是不容易复制的自定义对象(例如在Java中,自定义对象的变量是引用)? 顺便说一句,这来自我当前在Android中使用SurfaceView的工作。     

解决方法

        为了使所有变量保持同步并避免同步,您可以将变量放入不可变对象中,并整体进行更新。读取状态时,请保留一个这样的状态对象作为局部变量,这样可以保证在读取状态时没有其他人更新它。 这是一些示例代码(未经测试等)。如果旧值未在
setState
中读取或仅从一个线程访问,则一个volatile字段就足够了。但是在一般情况下(调用setState和新状态的多个线程取决于旧状态的值),使用AtomicReference可以确保不会丢失任何更新。
class Foo {
    private final AtomicReference<State> state = new AtomicReference<State>(new State(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,sh3 over and over...
        }
    }

    private class State {
        public final float sh1,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);

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

    public void run() {
        while (true) {
            State snapshot = state;
            // then do tons of stuff that uses sh1,float sh3) {
            this.sh1 = sh1;
            this.sh2 = sh2;
            this.sh3 = sh3;
        }
    }
}
    ,        有一个更简单的解决方法:
private volatile float sh1,sh3;  // note \"volatile\"
在Java内存模型中,允许线程缓存其他线程的值。 关键字“ 10”表示所有线程必须使用相同的变量值(即,所有线程都引用变量的相同内存位置)。与原语一起使用时,这意味着您不需要同步(尽管使用64位原语(其中float不是一个),您可能不需要同步,具体取决于您的JVM是32位还是64位) 您可能要观看部分/不一致的更新-当某些
sh
变量在另一个线程读取它们时进行更新。您可能希望通过对多个sh变量“ atomic”进行更新来同步更新以保持一致的状态。