如何在Java中创建一系列原子步骤?

时间:2016-07-13 13:24:59

标签: java multithreading concurrency synchronization atomicity

我有一系列步骤对两个类变量(例如sequence-of-stepsvar1)进行一些操作(var2)。此操作已计划在ScheduledExecutorService的帮助下每250毫秒运行一次。我想要做的是,每当我尝试从单独的线程引用var1var2中的值时,它们的状态应该与我执行的sequence-of-steps的原子性一致在他们。所以,假设我有以下代码:

mySchedulesExecutor.scheduleAtFixedRate(new Runnable() {
........................
// these are my 'sequence-of-steps'
var1 += 1;
var1 %= 4;
var2 += 25;
........................
}, 0, 250, TimeUnit.MILLISECONDS);

每当我想从其他任何地方读取var1var2的值时,它们应该与上述sequence-of-steps的原子性一致。实现这一目标的最佳方法是什么?

4 个答案:

答案 0 :(得分:1)

我认为针对您的情况的最佳实践是使用不可变对象,其中存储var1和var2的实际值。例如:

public class Holder {
   private final double var1;
   private final double var2;

//constructor, getters ommitted
}

public class OuterClass {

  private volatile Holder holder = new Holder(0, 0);

  private void calculateNew() {

    //new calculation omitted
    holder = new Holder(newVar1, newVar2);
  }

  public Holder getVars() {
    return holder;
  }
}

使用这个解决方案,你不需要使用任何丑陋的同步来保持一致性,所以它保证,来自外部的客户端将始终获得var1 a var2的一致性值。

我相信这个解决方案比使用synchronized更好,因为对于synchronized,你必须使用相同锁定,不仅用于编写变量,还用于用于读取。因此,当您编写新值时,没有其他线程可以读取原始值。正如我从你的原帖中所理解的那样,这不是你想要的行为。您只希望其他线程能够连续读取值,即使是旧值,但始终是一致的值。这样可以更好地使用不可变的习语,因为它会给你更好的响应(其他线程不必每次写入新值时等待)

答案 1 :(得分:0)

然后它们不应该作为类(即静态的,可能是易变的)变量访问。

有人可能会想到带有提交/回滚的事务。但我认为这里不需要。

实际上,人们需要一个具有字段var1var2的对象,可以通过原子方式获取和设置。然后人们总会有一个时间点保证。

该对象可能是不可变的,任何导致新对象的更改,如BigDecimal。

public class Data {
    public final int var1;
    public final int var2;
    public Data(int var1, int var2) {
        this.var1 = var1;
        this.var2 = var2;
    }
}

对于许多读者来说,一位作家在时间刻度上,会想到一个ReadWriteLock。 然而,Atomic也可能会这样做。

public class Holder {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock r = lock.readLock();
    private final Lock w = lock.writeLock();

    private static Holder instance = new Holder();

    public static Data getData() {
        ...
    }

    public static Data setData() {
    }
}

答案 2 :(得分:0)

您可以使用'synchronized'关键字(已屏蔽)

class App {
    int var0 = 0;
    int var1 = 0;

    public synchronized void apply(IntUnaryOperator f0, IntUnaryOperator f1) {
        this.var0 = f0.applyAsInt(this.var0);
        this.var1 = f0.applyAsInt(this.var1);
    }

    public synchronized int[] get() {
        return new int[] {var0, var1};
    }
}

或'锁定'类(已屏蔽)

class App {
    Lock lock = new ReentrantLock();
    int var0 = 0;
    int var1 = 0;

    public void apply(IntUnaryOperator f0, IntUnaryOperator f1) {
        lock.lock();
        try {
            this.var0 = f0.applyAsInt(this.var0);
            this.var1 = f0.applyAsInt(this.var1);
        } finally {lock.unlock();}
    }

    public int[] get() {
        lock.lock();
        try {
            return new int[]{var0, var1};
        } finally {lock.unlock();}
    }
}

或将'AtomicReference'用于不可变数据结构(非阻塞)

class App {
    AtomicReference<Data> data = new AtomicReference<>(new Data(0, 0));

    public void apply(IntUnaryOperator f0, IntUnaryOperator f1) {
        while (true) {
            Data oldData = data.get();
            Data newData = new Data(
                    f0.applyAsInt(oldData.var0),
                    f1.applyAsInt(oldData.var1)
            );
            if (data.compareAndSet(oldData, newData)) {
                break;
            }
        }
    }

    public int[] get() {
        Data current = data.get();
        return new int[]{current.var0, current.var1};
    }

    static class Data {
        final int var0;
        final int var1;

        public Data(int var0, int var1) {
            this.var0 = var0;
            this.var1 = var1;
        }
    }
}

或者实现类似'actor模型'(非阻塞+附加线程)的写入和非阻塞原子读取

class App {
    AtomicReference<Data> data = new AtomicReference<>(new Data(0, 0));
    BlockingQueue<IntUnaryOperator[]> mutateOperations
            = new LinkedBlockingQueue<>();
    Thread writer;
    {
        this.writer = new Thread(() -> {
            while (true) {
                try {
                    IntUnaryOperator[] mutateOp = mutateOperations.take();
                    Data oldData = data.get();
                    data.set(new Data(
                            mutateOp[0].applyAsInt(oldData.var0),
                            mutateOp[1].applyAsInt(oldData.var1)
                    ));
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        this.writer.start();
    }

    public void apply(IntUnaryOperator f0, IntUnaryOperator f1) {
        mutateOperations.add(new IntUnaryOperator[]{f0, f1});
    }

    public int[] get() {
        Data current = data.get();
        return new int[]{current.var0, current.var1};
    }

    static class Data {
        final int var0, var1;
        public Data(int var0, int var1) {
            this.var0 = var0;
            this.var1 = var1;
        }
    }
}

答案 3 :(得分:-3)

Java SE中有AtomicInteger API。