我有一系列步骤对两个类变量(例如var1
和var2
)执行一些操作(sequence-of-steps
)。此操作已计划在ScheduledExecutorService
的帮助下每 250 毫秒运行一次。我想做的是,每当我尝试从单独的线程引用 var1
和 var2
中的值时,它们的状态应该与我对它们执行的sequence-of-steps
的原子性相关联。所以,假设我有以下代码:
mySchedulesExecutor.scheduleAtFixedRate(new Runnable() {
........................
// these are my 'sequence-of-steps'
var1 += 1;
var1 %= 4;
var2 += 25;
........................
}, 0, 250, TimeUnit.MILLISECONDS);
每当我想从其他任何地方读取var1
和var2
的值时,它们都应该与上述sequence-of-steps
的原子性一致。实现这一目标的最佳方法是什么?
我相信适合您的情况的最佳做法是使用不可变对象,在其中存储 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 的一致性值。
我相信这个解决方案比使用同步更好,因为对于同步,您不仅必须使用相同的锁来写入变量,还必须用于读取。因此,当您写入新值时,没有其他线程可以读取原始值。正如我从您的原始帖子中了解到的那样,这不是您想要的行为。您只希望其他线程能够连续读取值,甚至是旧值,但始终一致的值。最好使用不可变的习语,因为它会给你更好的响应(其他线程不必每次写入新值时都等待)
那么它们不应该作为类(即静态的,可能是可变的)变量来访问。
人们可能会想到带有提交/回滚的事务。但我看不出这里有必要。
事实上,人们需要一个带有字段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;
}
}
对于许多读者来说,一个作家按时滴答作响,人们会想到ReadWriteLock。然而,原子也可能这样做。
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() {
}
}
您可以使用"syncd"关键字(已阻止)
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();}
}
}
或使用"原子引用"到不可变的数据结构(非阻塞)
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;
}
}
}
Java SE 中有 AtomicInteger API。