我们都知道在多线程操纵Map时,需要对Map数据结构进行同步,因为通过同步可以保证数据的一致性。但是同步化的同时,程序性能也往往会随之下降。在数据一致性与程序性能之间寻找平衡,是个挺纠结的事儿-_-|||…我们一定要在多线程环境下对Map进行同步吗?
不是的。例如应用场景对Map中的数据无一致性要求时,即可不做同步。当然,这种情况是很少发生的。那在要求Map中数据一致时怎样呢?这个…这个要具体问题具体分析啦!
对Map只有读操作
Map数据结构在初始化后,所有相关线程只做读操作,这时就没有必要进行同步了。因为不牵扯到数据修改,所以此时Map就相当于static的。我们在平时的应用场景中,还是存在一些这种情况的,只是我一时想不起来啦。哈哈。在只读情况下,HashMap与ConcurrentHashMap的性能还是有较大差距的。在10个线程,每个线程对Map数据读100000次的情况下,读取HashMap的耗时约是读取ConcurrentHashMap的耗时的1/2。
所以在对Map数据结构只有读操作的场景中,还是用HashMap更合算一些。
对Map既有读操作又有修改已有value值的操作(但每个线程都固定在Map中的特定Hash值区域)
Map数据结构在初始化后,所有相关线程既有读操作又有修改已有value值的操作,但是每个线程的操作都固定在Map中的特定区域,且相互不重叠。有图有真相:
如上图所示,存在4个线程:Thread1、Thread2、Thread3、Thread4。Thread1操作Map中的A区域,Thread2操作Map中的B区域,Thread3操作Map中的C区域,Thread4操作Map中的D区域。因为A、B、C、D四个区域互不重叠,所以不存在多个线程同时操作同一数据的情况。这种情况该如何处理呢?当然是用HashMap啦。可以把A、B、C、D这4个区域分别认为是线程Thread1、Thread2、Thread3、Thread4的local变量。
对Map既有读操作又有修改已有value值的操作同时还有Map.Entry的增减操作(但每个线程都固定在Map中的特定Hash值区域且Map的threshold不变)
-_-|||…情况比较BT啊。咱们还拿这个图说事儿:
假设Thread2在对B区域的Entry进行增删操作,Thread3在对C区域的Entry进行读写操作,Thread4在对D区域的Entry进行增删操作。那么Thread3会受到Thread2与Thread4的影响吗?在HashMap的情况下,只要threshold不变,就不会受到影响。也就是说,在这种场景下使用HashMap即可,无需使用ConcurrentHashMap。我们来根据HashMap的源码说明一下原因。
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
Transient Entry[] table;
HashMap是使用Entry[]类型的table属性来存储key-value的。Entry类有4个属性,分别是:final K key、V value、Entry<K, V> next、final int hash。其中Entry<K, V> next的值在HashMap中一般为null,只有在key出现hash冲突时才将新Entry的next属性指向原有的Entry,从而形成一个单项链表。而table[i]始终指向同一hash值的最新Entry。这是因为给HashMap增加key-value的方法是public V put(K key, V value)与private V putForNullKey(V value),而这两个方法在添加新Entry(而不是修改已有Entry)时都要调用void addEntry(int hash, K key, V value, int bucketIndex)方法。该方法源码如下:
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
我们可以看到第三行table[bucketIndex] = new Entry<K,V>(hash, key, value, e)。其中new一个Entry<K, V>时,其构造方法的第四个参数是e,即新Entry的next属性值。但是从第二行Entry<K,V> e = table[bucketIndex]我们知道e为原有的Entry,即table[bucketIndex]。而方法public V put(K key, V value)与private V putForNullKey(V value)在对HashMap中已有table[i]赋值时,均不改变其next属性。所以table[i]的next属性值在HashMap中要么为null,要么为相同hash值但不同key值的Entry。这就说明HashMap中table数组的各个Entry元素是无联系的。
不改变threshold是因为当threshold改变时牵扯到table的扩容,而table的扩容又牵扯到原有Entry[]的赋值等等。这些过程是非同步的,在多线程时容易出现问题。具体代码可以看void resize(int newCapacity)方法,这里我就不贴啦-_-|||…正是因为table数组中各个Entry的无关联性以及threshold的不变,使得在这种场景下可以使用非同步的HashMap。
那我们如何判断threshold是否会在运行过程中发生变化呢?这个问题问得很好-_-|||…这个就要看大家对应用场景的认识程度以及个人经验了。如果这两方面都不是很充分怎么办呢?哈哈,那就看人品啦。
另外HashMap有一个子类LinkedHashMap。这个LinkedHashMap是否可在这种场景使用呢?不行,因为LinkedHashMap的Entry有Entry<K, V>类型的两个属性:before与after。也就是说LinkedHashMap中各个Entry是相关联的。
对Map既有读操作又有修改已有value值的操作且每个线程都不固定在Map中的特定区域
Map数据结构在初始化后,所有相关线程既有读操作又有修改已有value值的操作,而且每个线程的操作都不固定在Map中的特定区域,即可能相互重叠。这时为了保证数据一致性,必须采用同步。那么我们是否一定要用ConcurrentHashMap来实现呢?不用的-_-|||…我们知道对同步进行优化的最有效手段就是不同步。哈哈。但是如果非要同步的话,那我们应该尽可能的缩小同步范围。如下图所示:
Thread1在对A区域的数据进行读写,Thread2与Thread3同时在对B区域的数据进行读写。如果我们使用ConcurrentHashMap,则Thread1、Thread2、Thread3需要顺序执行。但是我们发现,其实Thread1的执行,并不影响Thread2与Thread3的执行,所以Thread1完全可以和Thread2或Thread3并行执行。要实现这点,就需要把同步的范围从Map缩小的Value。为了演示,我分别对ConcurrentHashMap<Integer, Integer>与HashMap<Integer, MyInteger>进行测试。MyInteger的源码如下:
public class MyInteger {
public MyInteger(int i) {
super();
this.i = i;
}
public synchronized void setI(int i) {
this.i = i;
}
public synchronized int getI() {
return i;
}
private int i;
}
在10个线程,每个线程对Map数据读写100000次的情况下,读写HashMap<Integer, MyInteger>的耗时约是读写ConcurrentHashMap<Integer, Integer>的耗时的1/2。
对Map既有读操作又有写操作同时还有Map.Entry的增减操作且每个线程都不固定在Map中的特定区域
那…那就java.util.concurrent.ConcurrentHashMap<K,V>吧-_-|||…
相关推荐
总之,这个程序演示了如何在多线程环境下,使用C++的`map`容器和互斥量来实现并发控制,以及如何利用Windows API来创建定时器和管理线程。这种技术在需要高效并发处理和数据同步的软件设计中非常常见。
MFC 多线程及线程同步 MFC 多线程及线程同步 MFC 多线程及线程同步
13.如何使用临界区同步线程?(Visual C++编程 源代码)13.如何使用临界区同步线程?(Visual C++编程 源代码)13.如何使用临界区同步线程?(Visual C++编程 源代码)13.如何使用临界区同步线程?(Visual C++编程 ...
然而,多线程编程也带来了一些挑战,其中之一就是如何确保线程安全,即在多线程环境下正确地共享数据。这里我们将深入探讨"多线程临界段同步"的概念,以及如何通过API方式实现它,而不是依赖MFC(Microsoft ...
总结,Windows环境下的多线程编程涉及了从线程创建、同步到通信的多个方面,理解和掌握这些原理与技术对于开发高效、稳定的应用至关重要。在实践中,需要根据具体需求灵活运用,并注重性能优化和错误处理。
在编程领域,多线程是实现并发执行任务的重要方式,特别是在 Delphi 这样的面向对象的编程环境中。本文将深入探讨Delphi中的多线程和线程同步,并以"SortThreads"和"delphi-thread-gui"这两个示例项目为例,讲解如何...
1.使用三种VC的多线程同步方法编写一个多线程的程序(要求在屏幕上先显示Hello,再显示World)。 1)基于全局变量的多线程同步程序; 2)基于事件的多线程同步程序; 3)基于临界区的多线程同步程序。
这听起来感觉挺好,因为一旦同步线程,将在同步线程上花去一定的CPU时间片. 这一切都是真的,但是,不同步线程的条件是:只开两个线程,读线程在写线程之后进行操作.满足这两个条件,就可以不用进行线程同步啦! 如何...
在编程领域,尤其是在Windows平台下开发C++应用时,多线程技术是非常关键的一部分,它允许程序同时执行多个任务,从而提升系统效率。本示例着重讲解了VC++中的多线程同步,这是多线程编程中确保数据安全和正确性的...
在IT领域,多线程是程序设计中的一个重要概念,特别是在并发处理和性能优化时。标题“多线程示例,同时同步到进度条”表明我们要探讨的...通过阅读和分析这段代码,可以深入理解多线程环境下同步更新进度条的具体实现。
### 多线程环境下链表写入的同步问题解析 #### 一、引言 在多线程编程中,同步机制对于确保数据的一致性和程序的正确性至关重要。尤其是在链表这种动态数据结构上进行并发操作时,如果不采取适当的同步手段,可能...
通过合理地设计和使用 `ReaderWriterLockSlim` 锁机制、队列数据结构以及相应的接口和类,可以有效地解决多线程环境下的数据同步问题,提升数据处理的效率和系统的整体性能。这一实践不仅适用于特定的存储设备数据...
在多线程环境下,若没有同步措施,不同的线程可能同时增加计数值,导致最终结果错误。为解决此问题,我们可以使用互斥量来确保在同一时刻只有一个线程能够访问计数器。`std::mutex`是C++标准库提供的互斥量类型,...
"多线程的批量线程同步解决方案"这个标题暗示我们探讨的是如何在多线程环境下有效地管理和同步多个任务,确保数据一致性与程序正确性。下面将详细阐述相关知识点。 一、多线程基础 多线程是指在一个进程中同时执行...
然而,多线程环境下也带来了一些问题,尤其是资源竞争和数据一致性问题,这些问题需要通过线程同步机制来解决。本文将详细介绍如何通过临界区、互斥内核对象、事件内核对象和信号量内核对象来实现线程同步。 1. ...
线程安全是指在多线程环境中,程序能够正确地管理共享资源,避免数据不一致、死锁等错误。C#提供了多种机制来支持线程间的同步和通信,以确保线程安全。 #### 代码示例解析 在给定的代码片段中,一个名为`...
在单核CPU系统中,多线程是通过时间片轮转的方式实现并发,每个线程会获得一定的时间来执行其任务,而多核CPU则可以同时处理多个线程。线程同步是多线程编程中的关键概念,它防止了多个线程同时访问共享资源,避免了...
然而,多线程环境下的资源共享往往伴随着数据竞争问题,这时就需要引入线程同步机制,以确保共享资源的安全访问。本文将深入探讨多线程同步中的“临界区”概念及其在实际应用中的示例。 临界区是一种线程同步原语,...
通过分析和学习这个示例,你可以更好地理解如何在Windows环境下用C++实现多线程同步。 总之,C++的多线程功能使得开发者能够充分利用现代硬件的并行处理能力,而信号量作为一种有效的同步工具,可以防止数据竞争,...
在多线程编程中,同步是非常关键的概念,它确保了多个线程在访问共享资源时不会发生冲突。...在实际工作中,我们需要根据具体的需求和场景选择合适的同步机制,确保多线程环境下的代码正确性和性能。