Java:将对象突变限制在特定方法内



我目前正在尝试创建一个消息传递库,消息传递的原则之一是可变状态只能通过消息进行修改。将传递的"消息"是函数对象,这些对象采用"发送者"(创建消息(和"接收者"(工作线程/参与者/您从队列中处理消息的内容(。

工作线程定义如下,并且使用接口的自引用性质,因为工作线程可能具有它想要向消息发送者公开的状态,这要求发送者知道它的唯一类型。

public interface Worker<T extends Worker<T>> {
T getWorker(); //convert a Worker<T> to it's <T> since T extends Worker<T>
<S extends Worker<S>> void message(Message<S,T> msg);
}

其中<S>是发送消息的工作人员的类型。

消息定义如下:

public interface Message<S extends Worker<S>,R extends Worker<R>> {
void process(S sender, R thisWorker);
}

其中<S>是发送方的类类型,<R>是处理工作人员的类类型。正如接口和消息传递机制所期望的那样,该函数将能够修改thisWorker的状态。但是,如果消息直接改变sender,则会导致竞争条件,因为工作线程/线程之间没有同步(这就是使用消息开始的全部意义!

虽然我可以声明<S>是一个通用Worker<?>,但这将破坏消息以有意义的方式"回复"其发件人的能力,因为它不能再引用它的特定字段。

因此,如何确保除非在专门针对它的消息的上下文中,否则不会修改sender

T extends Worker<T>确实用于防止类返回非 Worker 类型。我可能只需要Worker<T>就可以侥幸逃脱并信任用户。

我特别要实现的是一个消息传递系统,其中消息是代码,这将避免在每个工作线程中对不同的消息类型进行任何特殊处理。但是,我可能意识到,如果我想强制执行某些消息协议,那么如果不遵守某些消息协议,就没有优雅的方法可以做到这一点,例如在 Swing EDT 上执行。

我不太明白你的设计。接口定义(如interface Worker<T extends Worker<T>>(中的递归泛型的确切用途是什么?interface Worker<T>还不够,仍然允许实现T getWorker()来获取实现Worker接口的类的具体类型吗?是否试图限制接口实现返回不是getWorker()Worker的东西?在这种情况下,我认为interface Worker<T extends Worker<?>>会造成更少的混乱。

因此,我如何确保sender不会被修改,除非 专门针对它的消息的上下文?

据我所知,防止在所需上下文之外调用对象方法的唯一方法是将对象方法的执行限制在客户端代码无法访问的单个线程中。这将要求对象检查当前线程作为每个方法中的第一件事。下面是一个简单的例子。它本质上是一个标准的生产者/消费者实现,其中正在修改的对象(不是Mailbox/消费者实现!(阻止在指定线程(使用传入消息的线程(以外的线程上修改自身。

class Mailbox {
private final BlockingQueue<Runnable> mQueue = new LinkedBlockingQueue<>();
private final Thread mReceiverThread = new Thread(() -> {
while (true) {
try {
Runnable job = mQueue.take();
job.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
public Mailbox() {
mReceiverThread.start();
}
public void submitMsg(Runnable msg) {
try {
mQueue.put(msg);
} catch (InterruptedException e) {
// TODO exception handling; rethrow wrapped in unchecked exception here in order to allow for use of lambdas.
throw new RuntimeException(e);
}
}
public void ensureMailboxThread(Object target) {
if (Thread.currentThread() != mReceiverThread) {
throw new RuntimeException("operations on " + target + " are confined to thread " + mReceiverThread);
}
}
}
class A {
// Example use
public static void main(String[] args) {
Mailbox a1Mailbox = new Mailbox();
Mailbox a2Mailbox = new Mailbox();
A a1 = new A(a1Mailbox);
A a2 = new A(a2Mailbox);
// Let's send something to a1 and have it send something to a2 if a certain condition is met.
// Notice that there is no explicit sender:
// If you wish to reply, you "hardcode" the reply to what you consider the sender in the Runnable's run().
a1Mailbox.submitMsg(() -> {
if (a1.calculateSomething() > 3.0) {
a2Mailbox.submitMsg(() -> a2.doSomething());
} else {
a1.doSomething();
}
});
}
private final Mailbox mAssociatedMailbox;
public A(Mailbox mailbox) {
mAssociatedMailbox = mailbox;
}
public double calculateSomething() {
mAssociatedMailbox.ensureMailboxThread(this);
return 3 + .14;
}
public void doSomething() {
mAssociatedMailbox.ensureMailboxThread(this);
System.out.println("hello");
}
}

让我重申强调的部分:由参与消息传递的每个单独的类在每个方法开始时验证当前线程。无法将此验证提取到通用类/接口,因为可以从任何线程调用对象的方法。例如,在上面示例中使用submitMsg提交的Runnable可以选择生成一个新线程并尝试修改该线程上的对象:

a1Mailbox.submitMsg(() -> {
Thread t  = new Thread(() -> a1.doSomething());
t.start();
});

但是,这将被阻止,但这只是因为检查是A.doSomething()本身的一部分。

最新更新