当前位置:Java -> ConcurrentHashMap中的并行性

ConcurrentHashMap中的并行性

ConcurrentHashMap 在多线程应用程序中被广泛使用。多线程应用程序的示例包括在线游戏应用程序和聊天应用程序,这些应用程序为应用程序增加了并发性的优势。为了使应用程序在性质上更具并发性,ConcurrentHashMap 引入了一个称为“并行性”的概念。

在本文中,我们将更多地学习关于 Concurrent Hashmaps 中的并行性。

什么是并行性?

基本上,并行计算将一个问题划分为子问题,通过并行地解决这些子问题,最终将子问题的结果合并起来。在这里,子问题将在独立的线程中运行。

Java 对 ConcurrentHashMap 的并行性支持

为了利用 ConcurrentHashMap 中的并行性,我们需要使用Java 1.8版本及更高版本。并行性不支持低于1.8版本的Java。

并行处理的常见框架

Java 引入了一个名为“fork and join”的框架,它将实现并行计算。它利用 java.util.concurrent.ForkJoinPool API 来实现并行计算。这个 API 被用来在 ConcurrentHashMap 中实现并行性。

ConcurrentHashMap 中的并行方法

ConcurrentHashMap 通过并行计算有效地使用并行性,其中使用了并行性阈值。它是一个数值,其默认值为2。

以下是具有并行性能力的方法:

  • forEach()
  • reduce()
  • reduceEntries()
  • forEachEntry()
  • forEachKey()
  • forEachValue()

ConcurrentHashMap 处理并行性的方式略有不同,如果查看上述方法的参数,您将能理解这一点。这些方法中的每一个都可以将并行性阈值作为参数。

首先,并行性是一个可选功能。通过在代码中添加适当的并行阈值值,我们可以启用此功能。

在不使用并行性的情况下使用 ConcurrentHashMap

让我们举一个替换 concurrenthashmap 中所有字符串值的示例。这是在不使用并行性的情况下完成的。

示例:

concurrentHashMap.forEach((k,v) -> v=””);

这非常简单,我们正在遍历 concurrenthashmap 中的所有条目,并将值替换为一个空字符串。在这种情况下,我们没有使用并行性。

在使用并行性的情况下使用 ConcurrentHashMap

示例:

concurrentHashMap.forEach(2, (k,v) -> v=””);

上面的例子遍历一个 ConcurrentHashMap 并用空字符串替换地图的值。forEach() 方法的参数是并行性阈值和一个功能接口。在这种情况下,问题将被划分为子问题。

问题是将并发哈希映射的值替换为一个空字符串。这通过将该问题划分为子问题实现,即为子问题创建单独的线程,并且每个线程将专注于替换值为一个空字符串。

启用并行性时会发生什么?

当并行性阈值被启用时,JVM 将创建线程,每个线程将运行以解决问题并合并所有线程的结果。这个值的重要性在于,如果记录的数量达到一定水平(阈值),那么只有在上述示例中 JVM 才会启用并行处理。如果在哈希映射中有多于一个记录,则应用程序将启用并行处理。

这是一个很棒的功能;我们可以通过调整阈值来控制并行性。通过这种方式,我们可以利用应用程序中的并行处理。

请看下面的另一个例子:

concurrentHashMap.forEach(10000, (k,v) -> v=””);

在这种情况下,并行性阈值为10,000,这意味着如果记录的数量少于10,000,JVM 将不会在替换值为一个空字符串时启用并行性。

图:没有并行性的完整代码示例

图:没有并行性的完整代码示例

图:具有并行性的完整代码示例图:具有并行性的完整代码示例

在上述示例中,并行性阈值为10,000。

并行处理性能比较

以下代码将使用空字符串替换映射中的所有值。 这个concurrenthash映射包含了超过10万条目。 让我们比较下面的代码在没有并行处理和启用并行处理的情况下的性能。

以下代码只是用空字符串替换映射中的所有值。

图:有无并行处理时代码的性能比较

运行上述代码之后,可以看到在常规forEach操作的情况下有一点性能提升。

无并行处理所需时间->20毫秒 

有并行处理所需时间->30毫秒

这是因为映射中的记录数量相当少。

但是如果我们给映射添加了1000万条记录,那么并行处理真的会赢! 这需要更少的时间处理数据。看看下面图片中的代码:

 图:有无并行处理时代码的性能阈值图:有无并行处理时代码的性能阈值

上面的代码将不使用并行处理将concurrenthashmap中的所有值替换为空字符串。 接下来,它使用并行处理将concurrenthashmap的所有值替换为字符串one。 这是输出:

无并行处理所需时间->537毫秒

有并行处理所需时间->231毫秒

可以看到,在并行处理的情况下,只需要一半的时间。

注意:上述数值不是固定的。 在不同系统中可能会产生不同的结果。

并行处理的线程转储分析

JVM使用ForkJoinPool框架在我们启用代码中的并行处理时,启用并行处理。 该框架根据当前处理的需求创建了一些工作线程。 让我们通过fastthread.io工具来查看启用并行处理时的线程转储分析。

图:fastThread报告显示了启用并行处理时的线程数

图:fastThread报告显示了启用并行处理时的线程数

图:通过启用并行处理显示相同堆栈跟踪的fastThread报告

图:fastThread报告显示了启用并行处理时相同堆栈跟踪

你可以从上面的图片中了解到它正在使用更多的线程。

线程过多的原因是它正在使用ForkJoinPool API。 这是负责在幕后实现“并行处理”的API。 当你看下一节时就会理解这一点的不同之处。

查看报告。

无并行处理的线程转储分析

让我们了解一下没有启用并行处理时的线程转储分析。

 图:fastThread报告显示了未启用并行处理时的线程数 图:fastThread报告显示了未启用并行处理时的线程数

Fig:fastThread 报告显示未启用并行性时具有相同堆栈跟踪 图:fastThread报告显示未启用并行性的相同堆栈跟踪

如果您仔细查看上面的图片,您会发现只使用了少数线程。在这种情况下,与上一张图片相比,只有35个线程。这种情况下有32个可运行的线程。但是,等待和定时等待线程分别为2和1。在这种情况下,可运行线程数量减少的原因是它没有调用ForkJoinPool API。

查看报告。

这样,fastthread.io工具可以非常智能地深入了解线程转储的内部。

摘要

我们着重研究了 concurrenthashmap 中的并行性以及如何在应用程序中使用此功能。此外,我们了解了在启用此功能时JVM会发生什么。并行性是一项很酷的功能,可以在现代并发应用程序中很好地使用。

推荐阅读: 20.常用HTTP状态码

本文链接: ConcurrentHashMap中的并行性