【问题标题】:Is it safe to put a stateless callback into BlockingQueue?将无状态回调放入 BlockingQueue 是否安全?
【发布时间】:2025-12-04 13:10:01
【问题描述】:

我有以下非常简单的回调接口和一个 POJO 类:

public interface Action{
    public void doAction();
}

public class Person{
     private String name;
     private String address;
     //...etc
     //GET, SET, toString
}

我将按如下方式使用它:

public class ActionExecutor{

    private static final Logger logger = LogManager.getLogger(ActionExecutor.class);
    private final BlockingQueue<Action> blockingQueue = new LinkedBlockingQueue(2000);

    public void execute(final Person p){
        //modify state of p in some way
        blockingQueue.put(new Action(){
            public void doAction(){
                logger.info("Execution started: " +p.toString );
                //do some other job
        });
    }
}

BlockingQueue这里用来实现生产者-消费者。

问题: 是否保证从BlockingQueue 执行操作的消费者线程将写入正确的日志消息? IE。它观察到Person 的正确状态?但我并不确定。

我认为不,这不能保证,因为在生产者进行的修改和生产者阅读之间没有顺序之前发生。

【问题讨论】:

  • 不,不保证。在调用 doAction 方法之前,Person p 的状态可能会改变。您可以获取 p.toString() 的字符串并将该字符串传递给您的 Action 构造函数并将其存储为 Action 实例的成员。
  • @bhspencer 所以我们只能安全地发布线程安全对象?
  • 我不知道你在这里发布是什么意思。我认为问题不在于线程安全,而在于状态。当您将 Action 放入队列时,它所引用的 Person 并未处于锁定状态,因此当您的操作最终运行时,此人的状态可能与您创建 Action 时的状态不同。人 p 很可能是线程安全的,但如果它是可变的,它的状态可以在你的控制下改变。
  • 也许你应该编写一个方法来克隆你的 Person 并将克隆的实例传递给你的 Action 构造。
  • 如果多个线程可能会更改队列中对象的状态,则您无法知道它的状态。阻塞队列的“阻塞”功能只适用于将引用加载到队列中。

标签: java multithreading blockingqueue


【解决方案1】:

答案是视情况而定。

如果您在此之后不再修改 Person 实例,那么它是有保证的。如in the docs 所述,存在发生前的关系:

内存一致性效果:与其他并发集合一样, 在将对象放入 BlockingQueue 之前线程中的操作 访问或删除该操作之后发生的操作 另一个线程中 BlockingQueue 中的元素。

但是,我仍然不会那样做。如果有人修改了 Person 实例之后回调已被放置在队列中,那么没有任何保证。您可以说,记录器保证打印的状态至少与添加到队列时一样最新。

所以消费者会看到这些修改:

public void execute(final Person p){
    //modify state of p in some way
    blockingQueue.put(new Action(){

但不是那些在那之后制作的。而且由于您在将p 传递给new Action() 对象后保留了它,所以我会避免这种情况。相反,我会这样做:

public void execute(final Person p){
    //modify state of p in some way
    blockingQueue.put(new Action() {
        final String executionName = p.toString();
        public void doAction(){
            logger.info("Execution started: " + executionName);
            //do some other job
    });
}

现在状态被捕获在一个最终变量中。无论p当时处于什么状态,消费者都会看到该值。

【讨论】:

  • 我相信您应该在 p 上实现一个方法来跟踪 p 的状态,方法是在 Person 类上实现一个将字符串添加到列表的方法,而不是在匿名 Action 类中执行 toString (将访问与对个人状态的其他更改同步,以保证每次状态更改一条消息)并使用公共方法(可能是 getStateChangeMes​​sage())提供对状态更改列表的访问。
  • @DwB,我不确定 Person 类是否适合这样做。问题中没有足够的信息来解决这个问题。如果它是一个简单的 POJO,那么它可能不是正确的地方。但这超出了这个问题的范围,我认为。
最近更新 更多