如何在Java中使一系列步骤原子化?

如何在Java中使一系列步骤原子化?,java,multithreading,concurrency,synchronization,atomicity,Java,Multithreading,Concurrency,Synchronization,Atomicity,我有一系列的步骤对两个类变量var1和var2进行操作。在ScheduledExecutorService的帮助下,此操作已计划每250毫秒运行一次。我想做的是,每当我试图从一个单独的线程引用var1和var2中的值时,它们的状态应该与我对它们执行的步骤序列的原子性一致。假设我有以下代码: mySchedulesExecutor.scheduleAtFixedRate(new Runnable() { ........................ // these are my 'seque

我有一系列的步骤对两个类变量var1和var2进行操作。在ScheduledExecutorService的帮助下,此操作已计划每250毫秒运行一次。我想做的是,每当我试图从一个单独的线程引用var1和var2中的值时,它们的状态应该与我对它们执行的步骤序列的原子性一致。假设我有以下代码:

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

每当我想从其他任何地方读取var1和var2的值时,它们都应该与上述步骤序列的原子性一致。实现这一点的最佳方法是什么?

Java SE中有API。

我认为针对您的情况的最佳实践是使用不可变对象,在其中存储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和var2的一致性值


我相信这个解决方案比使用synchronized要好,因为对于synchronized,您必须使用相同的锁,不仅用于写入变量,还用于读取。因此,当您编写新值时,其他线程无法读取原始值。正如我从你最初的帖子中了解到的,这不是你想要的行为。您只希望其他线程能够连续读取值,即使是旧值,但始终能够读取一致的值。这就是为什么最好使用不可变的习惯用法,因为它会给您更好的响应。其他线程不必在每次写入新值时等待,那么它们就不应该作为静态类(可能是可变变量)进行访问

您可能会想到具有提交/回滚的事务。但我认为没有必要这样做

事实上,我们需要一个具有var1和var2字段的对象,可以原子地获取和设置。然后,一个将始终有一个时间点保证

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

public class Data {
    public final int var1;
    public final int var2;
    public Data(int var1, int var2) {
        this.var1 = var1;
        this.var2 = var2;
    }
}
对于许多读者来说,一个按时写作的人,会想到读写写器。 然而,原子能也可以

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() {
    }
}

您可以使用“已同步”关键字阻止

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 model”的非阻塞+功能,并提供额外的线程用于写入和非阻塞原子读取

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;
        }
    }
}

使用显式锁?显式锁和易失性variables@JiriTousek:我已经阅读了您提到的基本并发的所有文档。但是,我不确定是否在一组操作周围使用synchronizedlock{…},它是否确保了上面Mena建议的原子性?unconsistent==一个变量已更改,但另一个变量未更改。当写入线程仍在同步块中时,其他线程将看不到这一点,并且只要写入线程正常退出,任何读取器都不会看到它。但是,如果线程遇到异常并使变量处于不一致状态,则在离开同步块后,此状态将保持有效,并且可能稍后被读取器线程看到。使用AtomicInteger如何解决问题中所述的问题?AtomicInteger不会使一组指令原子化。我同意错误答案。但是如果将两个var1和var2组合在一个对象中,他仍然可以使用java.util.concurrent.atomic和AtomicReference。AtomicReference允许对引用执行某些原子操作,但对如何对引用对象的字段执行操作没有影响。@latchy:好的。。你对同步块的原子性有什么看法?我还纠正了一个错误——你必须返回整个holder对象,而不仅仅是委托getter。谢谢你的回答。你的建议有道理。另外,如果您可以澄清同步块是否确保原子性。。。那太好了。再次感谢!同步块意味着,同一时间只有一个线程可以访问此块。不多也不少。作为开发人员,您必须确保原子性和与Java提供的选项的一致性。这可以通过使用相同的锁同步写入和访问值来实现,或者您可以使用我建议的解决方案,如果我正确理解了您的场景,这会更好。好的。。我现在将非常具体地回答我的问题。我想问的是,如果一个线程一旦获得了同步块上的锁,是否确保它将完成该块中的所有步骤,然后再将锁授予同一个锁上的另一个等待线程(但可能是另一个块)?