我目前正在尝试创建一个消息传递库,消息传递的原则之一是可变状态只能通过消息修改。将传递的“消息”是带有“发送者”(创建消息的)和“接收者”(工作人员/参与者/从队列中处理消息的对象)的函数对象。
Workers 定义如下,并使用接口(interface)的自引用性质,因为 Worker 可能具有想要向消息发送者公开的状态,这需要发送者了解其唯一类型。
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>
是处理worker的类类型。正如接口(interface)和消息传递机制所期望的那样,该函数将能够修改 thisWorker
的状态。但是,如果消息直接变异 sender
,会导致竞争条件,因为工作线程/线程之间没有同步(这就是使用消息的全部意义所在!)
虽然我可以声明<S>
成为通用Worker<?>
,这会破坏消息以有意义的方式“回复”发件人的能力,因为它无法再引用其特定字段。
因此,如何确保sender
除非在专门针对它的消息的上下文中,否则不会被修改?
T extends Worker<T>
确实用于防止类返回非 Worker 类型。我可能只需 Worker<T>
就可以逃脱惩罚并信任用户。
我特别想要实现的是一个消息传递系统,其中消息是代码,这将避免在每个工作人员中对不同消息类型进行任何特殊处理。然而,我可能意识到,如果我想强制执行某些消息协议(protocol)(例如在 Swing EDT 上执行),那么在不遵守某些消息协议(protocol)的情况下就没有优雅的方法来执行此操作。
最佳答案
我不太明白你的设计。接口(interface)定义中递归泛型的确切用途是什么,例如 interface Worker<T extends Worker<T>>
?不会interface Worker<T>
足够了,仍然允许执行 T getWorker()
获取实现 Worker
的类的具体类型界面?是否试图限制接口(interface)实现返回非 Worker
的内容来自getWorker()
?那么,我认为interface Worker<T extends Worker<?>>
会减少困惑。
据我所知,防止在所需上下文之外调用对象方法的唯一方法是将对象方法的执行限制在客户端代码无法访问的单个线程中。 这将要求对象在每个方法中首先检查当前线程。下面是一个简单的示例。它本质上是一个标准的生产者/消费者实现,其中正在修改的对象(不是Therefore, how can I ensure that
sender
won't be modified except in the context of a message addressed specifically to it?
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");
}
}
让我重申一下强调的部分:参与消息传递的每个单独的类都需要在每个方法开始时验证当前线程。无法将此验证提取到通用类/接口(interface),因为可以从任何线程调用对象的方法。例如,Runnable
使用 submitMsg
提交在上面的示例中,可以选择生成一个新线程并尝试修改该线程上的对象:
a1Mailbox.submitMsg(() -> {
Thread t = new Thread(() -> a1.doSomething());
t.start();
});
但是,这可以被阻止,但这只是因为检查是 A.doSomething()
的一部分。本身。
关于Java:将对象突变限制在特定方法内,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52195510/