Java中 ConcurrentHashMap 如何实现线程安全?

1,909 阅读4分钟

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

🌊 作者主页:海拥
🌊 作者简介:🥇HDZ核心组成员、🏆全栈领域优质创作者、🥈蝉联C站周榜前十
🌊 粉丝福利:进粉丝群每周送四本书(每位都有),每月抽送各种小礼品(掘金搪瓷杯、抱枕、鼠标垫、马克杯等)

ConcurrentHashMap是一个哈希表,支持检索的全并发和更新的高预期并发。此类遵循与 Hashtable 相同的功能规范,并包含 Hashtable 的所有方法。ConcurrentHashMap 位于 java.util.Concurrent 包中。

语法:

public class ConcurrentHashMap<K,V>
extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable

其中 K 指的是这个映射所维护的键的类型,V 指的是映射值的类型

ConcurrentHashmap 的需要:

  • HashMap虽然有很多优点,但不能用于多线程,因为它不是线程安全的。
  • 尽管 Hashtable 被认为是线程安全的,但它也有一些缺点。例如,Hashtable 需要锁定才能读取打开,即使它不影响对象。
  • n HashMap,如果一个线程正在迭代一个对象,另一个线程试图访问同一个对象,它会抛出 ConcurrentModificationException,而并发 hashmap 不会抛出 ConcurrentModificationException。

如何使 ConcurrentHashMap 线程安全成为可能?

  • java.util.Concurrent.ConcurrentHashMap类通过将map划分为segment来实现线程安全,不是整个对象需要锁,而是一个segment,即一个线程需要一个segment的锁。
  • 在 ConcurrenHashap 中,读操作不需要任何锁。

示例 1:

import java.util.*;
import java.util.concurrent.*;

// 扩展Thread类的主类
class GFG extends Thread {

	// 创建静态 HashMap 类对象
	static HashMap m = new HashMap();

	public void run()
	{

		// try 块检查异常
		try {

			// 让线程休眠 3 秒
			Thread.sleep(2000);
		}
		catch (InterruptedException e) {
		}
		System.out.println("子线程更新映射");
		m.put(103, "C");
	}
	public static void main(String arg[])
		throws InterruptedException
	{
		m.put(101, "A");
		m.put(102, "B");
		GFG t = new GFG();
		t.start();
		Set s1 = m.keySet();
		Iterator itr = s1.iterator();
		while (itr.hasNext()) {
			Integer I1 = (Integer)itr.next();
			System.out.println(
				"主线程迭代映射和当前条目是:"
				+ I1 + "..." + m.get(I1));
			Thread.sleep(3000);
		}
		System.out.println(m);
	}
}

输出:

主线程迭代映射和当前条目是:101...A
子线程更新映射
Exception in thread "main" java.util.ConcurrentModificationException
       at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1493)
       at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1516)
       at Main.main(Main.java:30)

输出说明:

上述程序中使用的类扩展了 Thread 类。让我们看看控制流。所以,最初,上面的java程序包含一个线程。当我们遇到语句 Main t= new Main() 时,我们正在为扩展 Thread 类的类创建一个对象。因此,每当我们调用 t.start() 方法时,子线程都会被激活并调用 run() 方法. 现在主线程开始执行,每当子线程更新同一个地图对象时,都会抛出一个名为 ConcurrentModificationException 的异常。    

现在让我们使用 ConcurrentHashMap 来修改上面的程序,以解决上述程序在执行时产生的异常。

示例 2:

import java.util.*;
import java.util.concurrent.*;

class Main extends Thread {
	static ConcurrentHashMap<Integer, String> m
		= new ConcurrentHashMap<Integer, String>();
	public void run()
	{
		try {
			Thread.sleep(2000);
		}
		catch (InterruptedException e) {
		}
		System.out.println("子线程更新映射");
		m.put(103, "C");
	}
	public static void main(String arg[])
		throws InterruptedException
	{
		m.put(101, "A");
		m.put(102, "B");
		Main t = new Main();
		t.start();
		Set<Integer> s1 = m.keySet();
		Iterator<Integer> itr = s1.iterator();
		while (itr.hasNext()) {
			Integer I1 = itr.next();
			System.out.println(
				"主线程迭代映射和当前条目是:"
				+ I1 + "..." + m.get(I1));
			Thread.sleep(3000);
		}
		System.out.println(m);
	}
}

输出

主线程迭代映射和当前条目是:101...A
子线程更新映射
主线程迭代映射和当前条目是:102...B
主线程迭代映射和当前条目是:103...C
{101=A, 102=B, 103=C}

输出说明:

上述程序中使用的 Class 扩展了Thread 类。让我们看看控制流,所以我们知道在 ConcurrentHashMap 中,当一个线程正在迭代时,剩余的线程可以以安全的方式执行任何修改。上述程序中主线程正在更新Map,同时子线程也在尝试更新Map对象。本程序不会抛出 ConcurrentModificationException。

Hashtable、Hashmap、ConcurrentHashmap的区别

HashtableHashmapConcurrentHashmap
我们将通过锁定整个地图对象来获得线程安全。它不是线程安全的。我们将获得线程安全,而无需使用段级锁锁定 Total Map 对象。
每个读写操作都需要一个objectstotal 映射对象锁。它不需要锁。读操作可以不加锁执行,写操作可以用段级锁执行。
一次只允许一个线程在地图上操作(同步)不允许同时运行多个线程。它会抛出异常一次允许多个线程以安全的方式操作地图对象
当一个线程迭代 Map 对象时,其他线程不允许修改映射,否则我们会得到 ConcurrentModificationException当一个线程迭代 Map 对象时,其他线程不允许修改映射,否则我们会得到 ConcurrentModificationException当一个线程迭代 Map 对象时,其他线程被允许修改地图,我们不会得到 ConcurrentModificationException
键和值都不允许为 NullHashMap 允许一个空键和多个空值键和值都不允许为 Null。
在 1.0 版本中引入在 1.2 版本中引入在 1.5 版本中引入

写在最后的

我已经写了很长一段时间的技术博客,并且主要通过掘金发表,这是我的一篇关于Java中 ConcurrentHashMap 如何实现线程安全。我喜欢通过文章分享技术与快乐。您可以访问我的博客: juejin.cn/user/204034… 以了解更多信息。希望你们会喜欢!😊

💌 欢迎大家在评论区提出意见和建议!💌