在 vavr 中使用 Map 时使用 AtomicReference 的惯用方法是什么?

What is an idiomatic way to use AtomicReference while using a Map in vavr?

这更像是一个问题,询问 Stack OverFlow 社区中经验丰富的手更喜欢什么方法。我想了解它,因为我不想编写不必要的样板代码。所以,把我的Q当作新手Q吧。

我的查询是从这个 and the answers on SOF. @Nandor 得到的提示已经阐明 AtomicReference 是要走的路。

在一个典型的应用程序中,class 使用一个 Map 来存储一些数据,几乎总是需要更新这个 Map。为此,我正在使用 Vavr 的 HashMap 实现。不用说,我在这里追求不变性

我正在处理的问题是在这里模拟的,使用虚构的 CourierSlottingMachine。它拥有一张地图,该地图将代理和该代理必须携带的包(可能是一个集合,但我们不知道,因为它是封装的)相关联。假设前台人员只能向机器添加一个新代理和她的包。

public class CourierSlottingMachine {

    private Map<AgentID, BagOfDeliveryItems>   allAgents;
    // ... other members, suitable constructor and methods
   
    public CourierDesk hereComesAnAgent(AgentID agentID,BagOfDeliveryItems bag) {

       // Two front-office clerks (== two threads) add two agents
       this.allAgents = this.allAgents.put(agentID,bag); // We don't mind overwriting the old bag
       return (this); 
      
    }
}

据我了解,'put' 函数的工作原理是不变性。 从 Vavr 的地图 API,put(K,V):

Returns: A new Map containing these elements and that entry.

因此,clerk(1) 添加了她的代理并将新生成的 Map 存储到 this.allAgents。 clerk(2) 也是如此。那么有可能(不总是)当函数 returns 和 this 时,只有一个职员的添加对外界可见。 换句话说,有可能一位前台职员后来发现她添加的代理(和包)从开槽机中丢失了(this)。

根据我的理解,这个问题的解决方案是 CourierSlottingMachine 应该将 AtomicRefence 保存到 allAgents 的 Map,而不仅仅是一个引用它,像这样:

public class CourierSlottingMachine {

    private AtomicReference<Map<AgentID, BagOfDeliveryItems>>   allAgents;
    // ... other members, suitable constructor and methods
   
    public CourierDesk hereComesAnAgent(AgentID agentID,BagOfDeliveryItems bag) {

       // Two front-office clerks (== two threads) add two agents
       this.allAgents = this.allAgents.updateAndGet(m -> m.put(agentID,bag)); 
       return (this); 
      
    }
}

我有三个主要问题:

Is my understanding correct?

嗯理解正确,代码有点偏。您不需要重新分配 this.allAgents(实际上这就是重点)- updateAndGet 就足够了,它对同一个变量进行操作。

...should always use an AtomicReference instead of just the Reference to that member variable...

AtomicReference只是其中一种方式,您可以使用锁、同步或其他机制。如果性能对你来说真的很重要,那么你应该考虑其他选择 - 如果你有非常高的争用(线程经常争夺这个变量)那么锁会比原子执行得更好。就模式而言,我建议采用最终字段的模式 - 由于最终字段,您将避免这样的错误(例如覆盖 vavr 列表变量)。

then do we really get any benefit of immutable collections offered by Vavr?

你确实受益,这是完全不同的事情,而且真的很了不起。我不擅长解释,但我们走吧。

假设您有一个 table(您的 CourierSlottingMachine class)并且 table 上有一个盒子(您的 Map)。箱子有一名守卫 (AtomicReference) 保护,所以只有一个人可以来用它做点什么。盒子本身设计成只有一个人可以操作它。

那么如果盒子已经被保护起来了,你真的需要一个守卫吗?不,所以在你的情况下你基本上没有好处,可以只使用 ConcurrentHashMap.

但现在想象一下,您让人们随身携带盒子,无论他们想去哪里(return从您的方法中获取 Map)。如果一个人拿走了你的盒子,那么他就有了自己的副本,他可以用它做任何他想做的事——毁掉它,把它切成两半,然后做成两个盒子,随便什么。然后最好的部分 - 如果他想要几分钟前的同一个盒子,他只需进来并再次拿走它。其他人也可以接受它并为所欲为,它不会影响其他人的盒子。那为什么要守卫呢?想象一下,您在盒子里添加了一些有价值的东西,并且您希望您的盒子立即展示 - 这就是守卫的目的。

现在想象一个相同的情况,如果盒子不是 vavr 盒子——它可以是一个普通的 java 盒子,甚至是一个并发的 java 盒子。永远只有一个盒子。人们可以接受它,但他们必须先 return 它才能让其他人接受它。不再有私人箱子,没有副本,没有 return 的安全点 - 其他人甚至可能偷走您之前放在那里的物品。

这个类比中途失败了,但我希望它至少能有所帮助:)