我当前正在尝试创建消息传递库,消息传递的原则之一是可变状态只能通过消息进行修改。将要传递的“消息”是带有“发送者”(创建消息)和“接收者”(工人/演员/您要从队列中处理消息的对象)的功能对象。
工作程序的定义如下,并使用了接口的自引用特性,因为工作程序可能具有要向消息发件人公开的状态,这需要发件人知道它的唯一类型。
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上执行),则没有一种优雅的方法可以不遵循某些消息协议。
答案 0 :(得分:1)
我不太了解您的设计。诸如interface Worker<T extends Worker<T>>
之类的接口定义中的递归泛型的目的到底是什么? interface Worker<T>
还不够,仍然允许实现T getWorker()
来获得实现Worker
接口的类的具体类型吗?是否试图限制接口实现从Worker
返回不是getWorker()
的内容?在那种情况下,我认为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");
}
}
让我重申一下强调的部分:在每个单独的方法开始时,由每个参与消息传递的类来验证当前线程。由于无法从任何线程调用对象的方法,因此无法将该验证提取到通用类/接口。例如,在上例中使用Runnable
提交的submitMsg
可以 选择生成新线程并尝试修改该线程上的对象:
a1Mailbox.submitMsg(() -> {
Thread t = new Thread(() -> a1.doSomething());
t.start();
});
但是,这是可以避免的,但这仅是因为支票是A.doSomething()
本身的一部分。