方式一、使用HashTable
Map<String, String> hashtable = new Hashtable<>();
实现原理是在增删改查的方法上使用了synchronized锁机制,在多线程环境下,无论是读数据还是修改数据,在同一时刻只能有一个线程在执行synchronized方法(所有线程竞争同一把锁),因为对整个表进行锁定。所以线程越多,对该map的竞争越激烈,效率越低。
方式二、使用Collections.synchronizedMap
Map<String, String> synchronizedHashMap =
Collections.synchronizedMap(new HashMap<String, String>());
调用synchronizedMap()方法后会返回一个SynchronizedMap类的对象,而在SynchronizedMap类中使用了synchronized同步关键字来保证对Map的操作是安全的。
实现原理是使用工具类里的静态方法,把传入的HashTable包装成同步的,即在增删改查的方法上增加了synchronized锁机制,每次操作hashmap都需要先获取到这个对象锁,这个对象锁加了synchronized修饰,其实现方式和HashTable差不多,效率也很低。
方式三、使用ConcurrentHashMap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
实现原理是HashTable是对整个表进行加锁,而ConcurrentHashMap是把表进行分段,初始情况下分成16段,每一段都有一把锁。当多个线程访问不同的段时,因为获取到的锁是不同的,所以可以并行访问。效率比HashTable
ConcurrentHashMap的实现——JDK7版本
🧸分段锁机制
左边是Hashtable的实现方式——锁整个hash表;右边是ConcurrentHashMap的实现方式——锁桶(或锁段)。
ConcurrentHashMap在对象中保存了一个Segment数组,即将hash表分成16个桶(默认值),而每个Segent元素即每个桶类似于一个Hashtable,诸如get、put、remove等常用操作只锁当前需要用到的桶。原来Hashtable只能一个线程进入,现在能同时16个进程进入(写进程才需要锁定,而读进程几乎不受限制)。
ConcurrentHashMap的实现——JDK8版本
在JDK1.7之前,ConcurrentHashMap是通过分段锁机制来实现的,所以其最大并发度受Segment的个数限制。因此,在JDK1.8中,ConcurrentHashMap的实现原理摒弃了这种设计,而是选择了与HahsMap蕾丝的数组+链表+红黑树的方式实现,而加锁则采用CAS和synchronized实现。
🧸CAS原理
一般地,锁分为悲观锁和乐观锁:
悲观锁对于同一个数据的并发操作,一定是为发生修改的。
乐观锁对于同一个数据的并发操作是不会修改的,在更新数据时会采用尝试更新不断重试的方式更新数据。
CAS(Compare And Swap,比较交换):
CAS有三个操作数,内存值V、预期值A、要修改的新值B,当且仅当预期值A 和 内存值V 相等时(A=V)才会将 内存值V修改为 要修改的新值B,否则什么都不做。
🧸ConcurrentHashMap的初始化
在构造ConcurrentHashMap时,并不会对hash表进行初始化,hash表的初始化是在插入第一个元素时进行的。在put操作时,如果检测到table为空或长度为0时,会调用initTable()方法对table进行初始化操作。
好啦,就先到这里吧~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信~