使用线程安全方法从类中的多个方法填充 Collection 或 Map

Posted

技术标签:

【中文标题】使用线程安全方法从类中的多个方法填充 Collection 或 Map【英文标题】:Populating a Collection or Map from multiple methods in a class using a thread safe approach 【发布时间】:2021-03-13 18:49:32 【问题描述】:

请告知解决以下问题的最佳方法:

我有一个java类如下:

@Service
public class ServiceAImpl 

    private final Set<String> someSet = new HashSet<>();
    private final List<Record> someList = new ArrayList<>();
    private final Map<String, String> someMap = new HashMap<>();

   //Loads XML as a document
   public Optional<Document> readXML(Document xmlDoc)

   

   //reads the root node and triggers the recursive extraction of the XML data
   public List<Record> extractRootNodeAndTriggersRecursive()
       extractXMLRecursively(elt);

   

   //recursively read hierarchical data in the XML
   public SomeObject extractXMLRecursively(Element elt)

   

   public SomeObject createReportFromTheMapOrList()

   


我知道实例成员(或列表/映射)不是线程安全的。

请问在需要通过多次调用同一类中定义的多个方法来更新或填充列表/集合/映射的逻辑时,最好的方法是什么。

因此,在另一个调用类(主 Springboot 应用程序)中,我将获取列表/设置/映射以在填充后处理它们。

我认为上述代码可能适用于按计划每周运行一次的独立应用程序,即并发性不会成为问题。但是,如果服务暴露给多个线程,我应该如何修改它?

有人遇到过这种情况吗?

任何想法或提示或参考将不胜感激。

谢谢。

【问题讨论】:

你可以使用线程安全的集合和映射,即CopyOnWriteArrayList,如果你写的很少,或者List&lt;Record&gt; someList = Collections.synchronizedList(new ArrayList&lt;&gt;())。对于地图,您可以使用ConcurrentHashMap,对于集合,存在ConcurrentSkipListSet,或者您也可以使用Map&lt;String, String&gt; someMap = Collections.newSetFromMap(new ConcurrentHashMap&lt;&gt;()) 感谢 fps。根据 nahaev 建议的答案,这与使用 ReentrantLock 相比如何? 锁允许一次同步访问多个集合,即将元素添加到三个集合并原子地执行该操作 【参考方案1】:

你可以使用ReentrantLock:

import java.util.concurrent.locks.ReentrantLock;

@Service
public class ServiceAImpl 

    private final Set<String> setNoDuplicateLabelsAllowed = new HashSet<>();
    private final Set<String> someSet = new HashSet<>();

    private final ReentrantLock lock = new ReentrantLock();

   //Loads XML as a document
   public Optional<Document> readXML(Document xmlDoc)
       lock.lock();
       try 
           // modify your collections safely here
        finally 
           lock.unlock();
       
   

   //reads the root node and triggers the recursive extraction of the XML data
   public List<Record> extractRootNodeAndTriggersRecursive()
       lock.lock();
       try 
           // modify your collections safely here
        finally 
           lock.unlock();
       
   

   //recursively read hierarchical data in the XML
   public SomeObject extractXMLRecursively(Element elt)
       lock.lock();
       try 
           // same here, etc.
        finally 
           lock.unlock();
       
   

请注意,您需要在每种方法中使用相同的锁。这保证了只有一个线程可以在特定时刻更改您的集合。除非释放锁,否则所有其他线程都将被阻塞,无论它们尝试以何种特定方法获取此锁。

【讨论】:

感谢您的快速回复 Nehaev。但是,如何将锁应用于以不同方法调用的多个列表/映射?是否,我需要锁定和解锁所有具有任何共享资源的方法,即map/list/set? 我已经更新了答案,我希望现在可以更清楚地了解多种方法的情况。 这是有道理的,感谢一百万。这意味着当spring实例化服务类时,一次只有一个线程能够使用它。对吗?我原帖中 fps 建议的选项呢? CopyOnWriteArrayList、ConcurrentHashMap 等如果您只有一个集合作为服务状态,则比锁定要好得多。如果需要跨多个集合(或任何其他对象)保持一致的状态,则必须使用锁。 您好 Nehaev,我已将逻辑放入并需要测试等 但是,我想知道一种用于解决并发问题的设计模式,如果有的话。我的同事建议我可以使用方法参数和返回类型,即将相关的集合/映射添加到任何更新/填充它的方法中,然后返回更新的集合/映射。我在想这会很混乱,因为当我遍历一个大型 XML 文件并验证其节点中的元素时,有多个地图和列表要更新。任何想法。

以上是关于使用线程安全方法从类中的多个方法填充 Collection 或 Map的主要内容,如果未能解决你的问题,请参考以下文章

从类 Ui_MainWindow(object) 的方法访问变量

使用 node express 从类中渲染方法

无法从类内部调用 ExtJS 类中的方法

从类中调用父方法

将互斥保护构建到 C++ 类中的线程安全方法?

机器人框架:无法使用 __eq__ 方法从类中获取关键字