Java中的ConcurrentHashMap教程

并发性是现代软件开发的一个重要方面,尤其是在多线程环境中。同时管理共享数据结构需要仔细考虑,以避免竞争条件并保持数据完整性。Java 并发编程武器库中的一项强大工具是 ConcurrentHashMap。

在这篇博文中,我们将深入探讨 ConcurrentHashMap 的内容、原因、时间和方式。

什么是ConcurrentHashMap?
ConcurrentHashMap 是 Java 5 中引入的 java.util.concurrent 包中的一个类。它旨在提供对映射数据结构的线程安全访问,允许多个线程并发读写,而不需要外部同步。它是 Java 中传统“HashMap”的增强版本,针对并发操作进行了优化。

为什么使用ConcurrentHashMap?
1. 线程安全:
使用 ConcurrentHashMap 的主要原因是为了确保多线程环境中的线程安全。传统的“HashMap”实现不是线程安全的,如果没有适当的同步,并发修改可能会导致未定义的行为。ConcurrentHashMap 通过提供无需外部同步的并发访问内部机制来解决此问题。

2. 改进的性能:
与使用可能导致争用并降低性能的外部同步机制(例如“同步”块或方法)不同,“ConcurrentHashMap”采用细粒度的锁和分区。这种设计允许多个线程同时对映射的不同段进行操作,从而在高争用的场景中获得更好的性能。

3.动态可扩展性:
`ConcurrentHashMap` 是动态可扩展的。随着线程数量的增加,Map会自动调整和扩展其内部结构,以适应不断增长的线程数量,保证高效的性能,而无需人工干预。

何时使用ConcurrentHashMap?
1. 多线程环境:
当您的应用程序涉及需要同时访问和修改共享映射的多个线程时,请使用“ConcurrentHashMap”。这在 Web 应用程序、服务器端处理或任何受益于并行性的应用程序等场景中很常见。

2. 高争用:
在共享资源争用较高的情况下,“ConcurrentHashMap”可以超越传统的同步机制,提供更好的可扩展性和响应能力。

3、读写操作:
如果您的应用程序需要在映射上进行频繁的读写操作,“ConcurrentHashMap”非常适合。它允许并发读取和写入而不需要锁,从而提高吞吐量。

如何使用ConcurrentHashMap:
1.创建实例:

import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();

要创建实例,可使用默认构造函数,或从多个构造函数中选择,这些构造函数允许您指定初始容量、负载系数和并发级别。

2.执行操作:
ConcurrentHashMap 提供了一组与 HashMap 类似的丰富方法。一些关键方法包括 `put()`、`get()`、`remove()`、`forEach()` 等。

concurrentMap.put("Key", 42);
int value = concurrentMap.get(
"Key");
concurrentMap.remove(
"Key");

3.原子操作:
在复合操作中使用 `putIfAbsent()`、`replace()`、`compute()` 和 `merge()` 等原子方法。

concurrentMap.putIfAbsent("Key", 42);
concurrentMap.replace(
"Key", 42, 43);
concurrentMap.compute(
"Key", (key, oldValue) -> oldValue + 1);

4.安全迭代:
在对地图进行遍历时,使用`forEach()`方法,即使在并发修改时也能确保安全遍历。

concurrentMap.forEach((key, value) -> System.out.println(key + ": " + value));

代码案例
提供 20 种可以使用 Java 中的 ConcurrentHashMap 的不同场景的简要说明和代码片段。

1.ConcurrentHashMap的基本用法:
   确保并发读写的线程安全操作。

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("One", 1);
map.get(
"One");

2. ConcurrentHashMap 中的原子更新:
   无需外部同步即可执行原子更新。

map.compute("One", (key, value) -> value + 1);

3.ConcurrentHashMap中的批量操作:
   使用“forEach”来并行处理条目。

map.forEach((key, value) -> processEntry(key, value));

4.ConcurrentHashMap中的条件移除:
   仅当满足特定条件时才删除条目。

map.remove("One", 1);

5. ConcurrentHashMap中的默认值:
   为不存在的键提供默认值。

map.computeIfAbsent("Two", key -> 2);

6.ConcurrentHashMap中的合并函数:
   使用自定义合并函数合并键的值。

map.merge("One", 10, Integer::sum);

7.ConcurrentHashMap中的搜索和更新:
   搜索键并自动更新其值。

map.search("One", (key, value) -> {
        if (value > 5) {
        map.replace(key, value, value * 2);
        }
        return value;
        });


8.ConcurrentHashMap中的分组方式:
   按特定属性对元素进行分组。

Map<Boolean, List<String>> groupedByLength = map.keySet()
        .stream()
        .collect(Collectors.groupingBy(key -> key.length() > 3));


9.ConcurrentHashMap中的并行处理:
   使用“forEachParallel”进行并行处理以提高性能。

map.forEachEntryParallel(2, entry -> processEntry(entry.getKey(), entry.getValue()));

10.ConcurrentHashMap中的键集操作:
    对键集执行原子操作。

map.keySet().removeIf(key -> key.length() > 3);


11.ConcurrentHashMap中的条件加法:
    仅当键不存在时才添加键值对。

map.putIfAbsent("Three", 3);

12.ConcurrentHashMap中的并发归约:
    使用“reduceEntries”进行并发减少。

int sum = map.reduceEntries(2, (entry) -> entry.getValue(), Integer::sum);

13. 迭代ConcurrentHashMap中的值:
    使用“forEachValue”迭代值。

map.forEachValue(2, value -> processValue(value));

14. 更新ConcurrentHashMap中的值:
    有条件地更新值。

map.replaceAll((key, value) -> value > 5 ? value * 2 : value);

15.ConcurrentHashMap中的Key过滤:
    基于谓词过滤键。

Set<String> filteredKeys = map.keySet().stream()
        .filter(key -> key.startsWith("O"))
        .collect(Collectors.toSet());

16.ConcurrentHashMap中的并发枚举:
    枚举条目,同时允许并发修改。

Enumeration<String> keys = map.keys();
        while (keys.hasMoreElements()) {
        String key = keys.nextElement();
        // process key
        }


17.ConcurrentHashMap中的并发搜索:
    使用“searchEntries”进行并发搜索操作。

String result = map.searchEntries(2, entry -> entry.getValue() > 5 ? entry.getKey() : null);

18.ConcurrentHashMap中自定义相等性:
    使用自定义“Object”作为覆盖“equals”和“hashCode”的键。

class CustomKey { 
    // 覆盖 equals 和 hashCode
 } 
ConcurrentHashMap< CustomKey , String > customMap = new ConcurrentHashMap<>();

19.ConcurrentHashMap中的线程安全初始化:
    确保线程安全的延迟初始化。

map.computeIfAbsent( "LazyInit" , key -> initializeValue());

20.ConcurrentHashMap中的并发替换:
    以原子方式替换键的现有值。

map.replace("One", 1, 10);

这些示例展示了“ConcurrentHashMap”在各种场景中的多功能性,从基本用法到高级并发操作。

最有用的初始化
ConcurrentHashMap最有用的是实现原子性质的初始化,可替代单例双锁初始化

if(a=null)
 syschronized(this){
   if(a=null)
      a=初始化方法
}

上述代码是为了保证初始化方法只执行一次,使用同步锁,如果两个以上多线程同时执行这段代码,都发现a是空,需要初始化,那么真正初始化的则只有一个线程执行,但是这种使用同步方式性能差。

使用ConcurrentHashMap的computeIfAbsent方法,只有映射中没有a值时,会执行初始化方法:

public ForumThread getThread(Long threadId) throws Exception {
        if (threadId == null || threadId == 0)
            return null;
        try {
            ForumThread  forumThread = atomicFactorys.computeIfAbsent(threadId, this::build);
            if(forumThread != null)//借助ConcurrentHashMap执行初始化方法,然后清除
                atomicFactorys.remove(threadId);
            return forumThread;
        } catch (Exception e) {
            throw new Exception(e);
        }
    }

另外一种初始化应用:

public class MessageListAction extends ModelListAction {
    private final static String module = MessageListAction.class.getName();
    private ConcurrentMap<String, Object> serviceCache = new ConcurrentHashMap<>();

    //初始化ForumMessageQueryService只执行一次
    public ForumMessageQueryService getForumMessageQueryService() {
        return (ForumMessageQueryService) serviceCache.computeIfAbsent(
"forumMessageQueryService",
                k -> WebAppUtil.getService(
"forumMessageQueryService",
                        this.servlet.getServletContext()));
    }

    public Object findModelIFByKey(HttpServletRequest request, Object key) {
        Debug.logVerbose(
"enter findModelByKey", module);

       
//使用ForumMessageQueryService
        ForumMessage forumMessage = getForumMessageQueryService().getMessage((Long) key);

        return forumMessage;
    }

,,
}

结论
在以并发性为主要考虑因素的世界中,"ConcurrentHashMap "作为管理共享映射数据结构的强大而高效的解决方案大放异彩。了解它的功能、优点和正确用法,对于在 Java 中构建可扩展且响应迅速的多线程应用程序至关重要。无论您是要处理高度竞争、频繁的读写操作,还是仅仅为了实现线程安全,`ConcurrentHashMap` 都是您并发编程工具包中的重要工具。