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

Java: Restricting object mutation to within a specific method

我目前正在尝试创建一个消息传递库,消息传递的原则之一是可变状态只能通过消息进行修改。将传递的 'messages' 是带有 'sender'(创建消息)和 'receiver'(worker/actor/what 让您从队列中处理消息的函数对象.)

Worker 定义如下,并使用接口的自引用性质,因为 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>是发送者的class类型,<R>是处理工人的class类型。正如界面和消息传递机制所预期的那样,该函数将能够修改 thisWorker 的状态。但是,如果消息直接变异 sender,则会导致竞争条件,因为 workers/threads 之间没有同步(这就是使用消息开始的全部意义!)

虽然我可以将 <S> 声明为通用 Worker<?>,但这会破坏以有意义的方式向其发件人发送消息 'reply' 的能力,因为它不能不再参考它的特定字段。

因此,我如何确保 sender 不会被修改,除非是在专门针对它的邮件的上下文中?

T extends Worker<T> 确实用于防止 class 返回非 Worker 类型。我可能只需要 Worker<T> 并信任用户即可。

我特别想要实现的是一个消息传递系统,其中消息是代码,这将避免在每个工作人员中对不同消息类型进行任何特殊处理的需要。但是,我可能意识到如果我想强制执行某些消息协议(例如在 Swing EDT 上执行),则没有任何优雅的方法可以在不遵守某些消息协议的情况下执行此操作。

我不太明白你的设计。 interface Worker<T extends Worker<T>> 等接口定义中递归泛型的确切用途是什么? interface Worker<T> 还不够吗,仍然允许 T getWorker() 的实现来获取实现 Worker 接口的 class 的具体类型?是否试图限制接口实现从 getWorker() 返回不是 Worker 的内容?在那种情况下,我认为 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?

据我所知,防止在所需上下文之外调用对象方法的唯一方法是将对象方法的执行限制在客户端代码无法访问的单个线程中。 这将要求对象在每个方法中首先检查当前线程。下面是一个简单的示例。它本质上是一个标准的 producer/consumer 实现,其中 正在修改的对象 不是 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");
    }

}

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

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

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