这篇文章,我们聊聊线程安全使用 HashMap 的四种技巧。

1方法内部:每个线程使用单独的 HashMap

如下图,tomcat 接收到到请求后,依次调用控制器 Controller、服务层 Service 、数据库访问层的相关方法。

每次访问服务层方法 serviceMethod 时,都会在方法体内部创建一个单独的 HashMap , 将相关请求参数拷贝到 HashMap 里,然后调用 DAO 方法进行数据库操作。

每个 HTTP 处理线程在服务层方法体内部都有自己的 HashMap 实例,在多线程环境下,不需要对 HashMap 进行任何同步操作。

这也是我们使用最普遍也最安全的的方式,是 CRUD 最基本的操作。

2 配置数据:初始化写,后续只提供读

系统启动之后,我们可以将配置数据加载到本地缓存 HashMap 里 ,这些配置信息初始化之后,就不需要写入了,后续只提供读操作。

上图中显示一个非常简单的配置类 SimpleConfig ,内部有一个 HashMap 对象 configMap 。构造函数调用初始化方法,初始化方法内部的逻辑是:将配置数据存储到 HashMap 中。

SimpleConfig 类对外暴露了 getConfig 方法 ,当 main 线程初始化 SimpleConfig 对象之后,当其他线程调用 getConfig 方法时,因为只有读,没有写操作,所以是线程安全的。

3 读写锁:写时阻塞,并行读,读多写少场景

读写锁是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,而写锁则是互斥锁。

它的规则是:读读不互斥,读写互斥,写写互斥,适用于读多写少的业务场景。

我们一般都使用 ReentrantReadWriteLock ,该类实现了 ReadWriteLock 。ReadWriteLock 接口也很简单,其内部主要提供了两个方法,分别返回读锁和写锁 。

 public interface ReadWriteLock {
//获取读锁
Lock readLock();
//获取写锁
Lock writeLock();
}

读写锁的使用方式如下所示:

  1. 创建 ReentrantReadWriteLock 对象 , 当使用 ReadWriteLock 的时候,并不是直接使用,而是获得其内部的读锁和写锁,然后分别调用 lock / unlock 方法 ;
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  1. 读取共享数据 ;
Lock readLock = readWriteLock.readLock();
readLock.lock();
try {
// TODO 查询共享数据
} finally {
readLock.unlock();
}
  1. 写入共享数据;
Lock writeLock = readWriteLock.writeLock();
writeLock.lock();
try {
// TODO 修改共享数据
} finally {
writeLock.unlock();
}

下面的代码展示如何使用 ReadWriteLock 线程安全的使用 HashMap :

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockCache { // 创建一个 HashMap 来存储缓存的数据
private Map<String, String> map = new HashMap<>(); // 创建读写锁对象
private ReadWriteLock rw = new ReentrantReadWriteLock(); // 放对象方法:向缓存中添加一个键值对
public void put(String key, String value) {
// 获取写锁,以确保当前操作是独占的
rw.writeLock().lock();
try {
// 执行写操作,将键值对放入 map
map.put(key, value);
} finally {
// 释放写锁
rw.writeLock().unlock();
}
} // 取对象方法:从缓存中获取一个值
public String get(String key) {
// 获取读锁,允许并发读操作
rw.readLock().lock();
try {
// 执行读操作,从 map 中获取值
return map.get(key);
} finally {
// 释放读锁
rw.readLock().unlock();
}
}
}

使用读写锁操作 HashMap 是一个非常经典的技巧,消息中间件 RockeMQ NameServer (名字服务)保存和查询路由信息都是通过这种技巧实现的。

另外,读写锁可以操作多个 HashMap ,相比 ConcurrentHashMap 而言,ReadWriteLock 可以控制缓存对象的颗粒度,具备更大的灵活性。

4 Collections.synchronizedMap : 读写均加锁

如下代码,当我们多线程使用 userMap 时,

static Map<Long, User> userMap = Collections.synchronizedMap(new HashMap<Long, User>());

进入 synchronizedMap 方法:

public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}

SynchronizedMap 内部包含一个对象锁 Object mutex ,它本质上是一个包装类,将 HashMap 的读写操作重新实现了一次,我们看到每次读写时,都会用 synchronized 关键字来保证操作的线程安全。

虽然 Collections.synchronizedMap 这种技巧使用起来非常简单,但是我们需要理解它的每次读写都会加锁,性能并不会特别好。

5 总结

这篇文章,笔者总结了四种线程安全的使用 HashMap 的技巧。

1、方法内部:每个线程使用单独的 HashMap

这是我们使用最普遍,也是非常可靠的方式。每个线程在方法体内部创建HashMap 实例,在多线程环境下,不需要对 HashMap 进行任何同步操作。

2、 配置数据:初始化写,后续只提供读

中间件在启动时,会读取配置文件,将配置数据写入到 HashMap 中,主线程写完之后,以后不会再有写入操作,其他的线程可以读取,不会产生线程安全问题。

3、读写锁:写时阻塞,并行读,读多写少场景

读写锁是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,而写锁则是互斥锁。

它的规则是:读读不互斥,读写互斥,写写互斥,适用于读多写少的业务场景。

使用读写锁操作 HashMap 是一个非常经典的技巧,消息中间件 RockeMQ NameServer (名字服务)保存和查询路由信息都是通过这种技巧实现的。

4、Collections.synchronizedMap : 读写均加锁

Collections.synchronizedMap 方法使用了装饰器模式为线程不安全的 HashMap 提供了一个线程安全的装饰器类 SynchronizedMap。

通过SynchronizedMap来间接的保证对 HashMap 的操作是线程安全,而 SynchronizedMap 底层也是通过 synchronized 关键字来保证操作的线程安全。


如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!

线程安全使用 HashMap 的四种技巧的更多相关文章

  1. HashMap的四种遍历方法,及效率比较(简单明了)

    https://yq.aliyun.com/ziliao/210955 public static void main(String[] args) { HashMap<Integer, Str ...

  2. HashMap的四种遍历!

    HashMap的四种遍历 import java.util.Collection; import java.util.HashMap; import java.util.Map; import jav ...

  3. 遍历HashMap的四种方法

    public static void main(String[] args) { Map<String,String> map=new HashMap<String,String&g ...

  4. 遍历HashMap的四种方式

    转至:https://www.cnblogs.com/Berryxiong/p/6144086.html public static void main(String[] args) { Map< ...

  5. 遍历hashmap 的四种方法

    以下列出四种方法 public static void main(String[] args) { Map<String,String> map=new HashMap<String ...

  6. java HashMap的四种获取key,value的方式

    初学java不久,我觉得这样将学到的东西总结下来非常好,如果有一天有些地方忘记了 可以回过头来翻看,不用来回的找,非常好,也是一个很好的习惯 今天主要将记录获取hashMap的key,value的几种 ...

  7. c# ThreadPool 判断子线程全部执行完毕的四种方法

    1.先来看看这个 多线程编程 多线程用于数据采集时,速度明显很快,下面是基本方法,把那个auto写成采集数据方法即可. using System; using System.Collections.G ...

  8. HashMap的四种遍历方式

    package com.xt.map; import java.util.HashMap; import java.util.Iterator; import java.util.Map; impor ...

  9. Android 四种常见的线程池

    引入线程池的好处 1)提升性能.创建和消耗对象费时费CPU资源 2)防止内存过度消耗.控制活动线程的数量,防止并发线程过多. 我们来看一下线程池的简单的构造 public ThreadPoolExec ...

  10. java自带的四种线程池

    java预定义的哪四种线程池? newSingleThreadExexcutor:单线程数的线程池(核心线程数=最大线程数=1) newFixedThreadPool:固定线程数的线程池(核心线程数= ...

随机推荐

  1. 如何使用DevEco Studio创建Native C++应用

    简介 本篇主要介绍如何使用DevEco Studio for OpenAtom OpenHarmony (以下简称"OpenHarmony")创建一个Native C++应用.应用 ...

  2. Go 语言中切片的使用和理解

    切片与数组类似,但更强大和灵活.与数组一样,切片也用于在单个变量中存储相同类型的多个值.然而,与数组不同的是,切片的长度可以根据需要增长和缩小.在 Go 中,有几种创建切片的方法: 使用[]datat ...

  3. Web Audio API 第4章 音调与频域

    音调与频域 此章中如果对音乐部分不感兴趣,可忽略 代码部分也没有更多的新 api ,重要的还是相关的物理与声音的相关知识 到目前为止我们已经学过了声音的基础属性:定时与音量.为了能处理更复杂的的情况, ...

  4. 堡垒机安装pytorch,mmcv,mmclassification,并训练自己的数据集

    堡垒机创建conda环境,并激活进入环境 conda create -n mmclassification python=3.7 conda activate mmclassification 堡垒机 ...

  5. 力扣498(java)-对角线遍历(中等)

    题目: 给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素. 输入:mat = [[1,2,3],[4,5,6],[7,8,9]]输出:[1,2,4 ...

  6. 搭建Hadoop环境

    搭建Hadoop环境 一.虚拟机的安装 二. 安装JDK 1.下载jdk wget https://download.java.net/openjdk/jdk8u41/ri/openjdk-8u41- ...

  7. 10种编程语言实现Y组合子

    简介: Y组合子是Lambda演算的一部分,也是函数式编程的理论基础.它是一种方法/技巧,在没有赋值语句的前提下定义递归的匿名函数,即仅仅通过Lambda表达式这个最基本的"原子" ...

  8. AI运动:阿里体育端智能最佳实践

    简介: 过去一年,阿里体育技术团队在端智能方面不断探索,特别在运动健康场景下实现了实践落地和业务赋能,这就是AI运动项目.AI运动项目践行运动数字化的理念,为运动人口的上翻提供了重要支撑,迈出了阿里体 ...

  9. 编码原则 : DRY, KISS, YAGNI, S.O.L.I.D

    Dont Repeat Yourself. Keep is Simple, Stupid. You Ain't Gonna Need It.  你不需要它 ( 不试图添加你认为以后可能需要的代码,适可 ...

  10. 2019-7-2-WPF-通过-ReadyToRun-提升性能

    title author date CreateTime categories WPF 通过 ReadyToRun 提升性能 lindexi 2019-07-02 10:29:55 +0800 201 ...