使用JDK的同步容器时,应该避免那些坑?
摘要:在使用JDK中的同步容器时,应该尽量避免哪些坑
本文分享自华为云社区《【高并发】亿级流量高并发秒杀系统商品“超卖”了,只因使用的JDK同步容器中存在这两个巨大的坑!!(踩坑实录)》,作者:冰 河。
同步容器与并发容器
在JDK中,总体上可以将容器分为同步容器和并发容器。

同步容器一般指的是JDK1.5版本之前的线程安全的容器,同步容器有个最大的问题,就是性能差,容器中的所有方法都是用synchronized保证互斥,串行度太高。在JDK1.5之后提供了性能更高的线程安全的容器,我们称之为并发容器。
无论是同步容器还是并发容器,都可以将其分为四个大类,分别为:List、Set、Map和Queue,如下所示。

接下来,我们就简单聊聊使用JDK中的同步容器时,究竟要注意避免哪些坑。
同步容器的坑
在Java中,容器可以分为四大类:List、Set、Map和Queue,但是在这些容器中,有些容器并不是线程安全的,例如我们经常使用的ArrayList、HashSet、HashMap等等就不是线程安全的容器。
那么,根据我们在【精通高并发系列】专栏学习的并发编程知识,如何将一个不是线程安全的容器变成线程安全的呢? 相信有很多小伙伴都能够想到一个办法,那就是把非线程安全的容器的方法都加上synchronized锁,使这些方法的访问都变成同步的。
没错,这确实是一种解决方案,例如,我们自定义一个 CustomSafeHashMap类,内部维护着一个HashMap,外界对HashMap的访问都加上了synchronized锁,以此来保证方法的原子性,例如下面的伪代码所示。
public class CustomSafeHashMap<K, V>{
private Map<K, V> innerMap = new HashMap<K, V>();
public synchronized void put(K k, V v){
innerMap.put(k, v);
}
public synchronized V get(K k){
return innerMap.get(k);
}
}
看到这里,一些小伙伴可能会想:是不是所有的非线程安全的容器类都可以通过为方法添加synchronized锁来保证方法的原子性,从而使容器变得安全呢?
是的,我们可以通过为非线程安全的容器方法添加synchronized锁来解决容器的线程安全问题。其实,在JDK中也是这么做的。例如,在JDK中提供了线程安全的List、Set和Map,它们都是通过synchronized锁保证线程安全的。
例如,我们可以通过如下方式创建线程安全的List、Set和Map。
List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
Map map = Collections.synchronizedMap(new HashMap());
那么,说了这么多,同步容器有哪些坑呢?
坑一:竞态条件问题
在使用同步容器时需要注意的是,在并发编程中,组合操作要时刻注意竞态条件,例如下面的代码。
public class CustomSafeHashMap<K, V>{
private Map<K, V> innerMap = new HashMap<K, V>();
public synchronized void put(K k, V v){
innerMap.put(k, v);
}
public synchronized V get(K k){
return innerMap.get(k);
}
public synchronized void putIfNotExists(K k, V v){
if(!innerMap.containsKey(k)){
innerMap.put(k, v);
}
}
}
其中,putIfNotExists()方法就包含组合操作。在高并发环境中,存在组合操作的方法可能就会存在竞态条件。
也就是说,在并发编程中,即使每个操作都能保证原子性,也不能保证组合操作的原子性。
坑二:使用迭代器遍历容器
一个容易被人忽略的坑就是使用迭代器遍历容器,对容器中的每个元素调用一个方法,这就存在了并发问题,这些组合操作不具备原子性。
例如下面的代码,通过迭代器遍历同步List,对List集合中的每个元素调用format()方法。
List list = Collections.synchronizedList(new ArrayList());
Iterator iterator = list.iterator();
while (iterator.hasNext()){
format(iterator.next());
}
此时,会存在并发问题,这些组合操作并不具备原子性。
如何解决这个问题呢?一个很简单的方式就是锁住list集合,如下所示。
List list = Collections.synchronizedList(new ArrayList());
synchronized(list){
Iterator iterator = list.iterator();
while (iterator.hasNext()){
format(iterator.next());
}
}
这里,为何锁住list集合就能够解决并发问题呢?
这是因为在Collections类中,其内部的包装类的公共方法锁住的对象是this,其实就是上面代码中的list,所以,我们对list加锁后,就能够保证线程的安全性了。
在Java中,同步容器一般都是基于synchronized锁实现的,有些是通过包装类实现的,例如List、Set、Map等。有些不是通过包装类实现的,例如Vector、Stack、HashTable等。
对于这些容器的遍历操作,一定要为容器添加互斥锁保证整体的原子性。
使用JDK的同步容器时,应该避免那些坑?的更多相关文章
- Java并发—同步容器和并发容器
简述同步容器与并发容器 在Java并发编程中,经常听到同步容器.并发容器之说,那什么是同步容器与并发容器呢?同步容器可以简单地理解为通过synchronized来实现同步的容器,比如Vector.Ha ...
- java并发:同步容器&并发容器
第一节 同步容器.并发容器 1.简述同步容器与并发容器 在Java并发编程中,经常听到同步容器.并发容器之说,那什么是同步容器与并发容器呢?同步容器可以简单地理解为通过synchronized来实现同 ...
- 013-并发编程-java.util.concurrent.locks之-AbstractQueuedSynchronizer-用于构建锁和同步容器的框架、独占锁与共享锁的获取与释放
一.概述 AbstractQueuedSynchronizer (简称AQS),位于java.util.concurrent.locks.AbstractQueuedSynchronizer包下, A ...
- Java并发机制(5)--同步容器与并发容器
Java并发编程:同步容器整理自:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3933404.html1.同步容器出现原因 常用的ArrayList,Lin ...
- JAVA同步容器和并发容器
同步容器类 同步容器类的创建 在早期的JDK中,有两种现成的实现,Vector和Hashtable,可以直接new对象获取: 在JDK1.2中,引入了同步封装类,可以由Collections.sync ...
- Java并发——同步容器与并发容器
同步容器类 早期版本的JDK提供的同步容器类为Vector和Hashtable,JDK1.2 提供了Collections.synchronizedXxx等工程方法,将普通的容器继续包装.对每个共有方 ...
- Java 同步容器和并发容器
同步容器(在并发下进行迭代的读和写时并不是线程安全的) Vector.Stack.HashTable Collections类的静态工厂方法创建的类(如Collections.synchr ...
- 【Java并发编程二】同步容器和并发容器
一.同步容器 在Java中,同步容器包括两个部分,一个是vector和HashTable,查看vector.HashTable的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并 ...
- Java的同步容器和并发容器
前言: 之前在介绍Java集合的时候说到,java提供的实现类很少是线程安全的.只有几个比较古老的类,比如Vector.Hashtable等是线程安全的,尤其是Hashtable,古老到连命名规范都没 ...
随机推荐
- 安装vsFTP到CentOS(YUM)
运行环境 系统版本:CentOS Linux release 7.3.1611 (Core) 软件版本:vsftpd-3.0.2 硬件要求:无 安装过程 1.安装YUM-EPEL存储库 YUM-EPE ...
- vue项目|在弹窗中引入uchart图表子组件不显示
为了解决uchart作为子组件在主组件里引用但不显示的情况,(同样适用于弹窗之中)目前有三种方法. 1-解决方式 1>如果你使用的uchart子组件是从官方拿的例子:进入到uchart子组件将o ...
- ML第3周学习小结
本周收获 总结一下本周学习内容: 1.学习了<深入浅出Pandas>的第五章:Pandas高级操作的三个内容 复杂查询 数据类型转换 数据排序 我的博客链接: Pandas复杂查询.数据类 ...
- 聊聊C#中的Mixin
写在前面 Mixin本意是指冰淇淋表面加的那些草莓酱,葡萄干等点缀物,它们负责给冰淇淋添加风味.在OOP里面也有Mixin这个概念,和它的本意相似,OOP里面的Mixin意在为类提供一些额外功能--在 ...
- 「JOISC 2020 Day1」汉堡肉
我终于学会打开机房的LOJ了! description LOJ3272 有\(n(n<=2*10^5)\)个矩形,让你找\(k(k<=4)\)个点可以覆盖所有矩形(点可重复),输出一种方案 ...
- 附001.Python多版本环境管理
一 环境背景 由于Python的版本过多,且不同版本之间差异性较大.同时又因系统底层需要调用当前版本Python,所以不能随意变更当前系统Python版本.因此,在多版本共存的情况下,Python多环 ...
- Simple, Fast Malicious Multiparty Private Set Intersection-解读
文本记录阅读该论文的笔记. 这是文章框架,来自视频. 介绍 本文主要解决恶意攻击下安全的多方PSI,主要用到两大技术OPPRF和OKVS,构造合谋和不合谋的协议. 基础知识 OPPRF 这部分在OPR ...
- CabloyJS全栈开发之旅(1):NodeJS后端编译打包全攻略
背景 毋庸置疑,NodeJS全栈开发包括NodeJS在前端的应用,也包括NodeJS在后端的应用.CabloyJS前端采用Vue+Framework7,采用Webpack进行打包.CabloyJS后端 ...
- neo4j中重复节点问题
neo4j中重复节点问题 neo4j中,在导入csv文件时,可能会出现有重复节点的现象.可以分为以下步骤解决:1.先查看下某个标签下的节点总数match (n:PERSON) return count ...
- 2021.05.04【NOIP提高B组】模拟 总结
T1 题目大意, \(S_{i,j}=\sum_{k=i}^j a_k\) ,求 \(ans=\min\{ S_{i,j}\mod P|S_{i,j}\mod P\ge K \}\) 其中 \(i\l ...