在 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);
}
}
我有三个主要问题:
- 我的理解正确吗?
- 如果是这样,那么任何具有可更新成员变量的 class 暴露给多个并发线程的修改操作,应该始终使用 AtomicReference 而不仅仅是对该成员变量的引用:这是模式吗我们应该始终遵循?
- 如果我一直在使用 AtomicReference,那么我们真的能从 Vavr 提供的不可变集合中获益吗?这可能是一个非常基本的问题,但我需要填补我理解中的漏洞,如果有的话。
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 的安全点 - 其他人甚至可能偷走您之前放在那里的物品。
这个类比中途失败了,但我希望它至少能有所帮助:)
这更像是一个问题,询问 Stack OverFlow 社区中经验丰富的手更喜欢什么方法。我想了解它,因为我不想编写不必要的样板代码。所以,把我的Q当作新手Q吧。
我的查询是从这个
在一个典型的应用程序中,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);
}
}
我有三个主要问题:
- 我的理解正确吗?
- 如果是这样,那么任何具有可更新成员变量的 class 暴露给多个并发线程的修改操作,应该始终使用 AtomicReference 而不仅仅是对该成员变量的引用:这是模式吗我们应该始终遵循?
- 如果我一直在使用 AtomicReference,那么我们真的能从 Vavr 提供的不可变集合中获益吗?这可能是一个非常基本的问题,但我需要填补我理解中的漏洞,如果有的话。
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 的安全点 - 其他人甚至可能偷走您之前放在那里的物品。
这个类比中途失败了,但我希望它至少能有所帮助:)