摘要:在使用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的同步容器时,应该避免那些坑?的更多相关文章

  1. Java并发—同步容器和并发容器

    简述同步容器与并发容器 在Java并发编程中,经常听到同步容器.并发容器之说,那什么是同步容器与并发容器呢?同步容器可以简单地理解为通过synchronized来实现同步的容器,比如Vector.Ha ...

  2. java并发:同步容器&并发容器

    第一节 同步容器.并发容器 1.简述同步容器与并发容器 在Java并发编程中,经常听到同步容器.并发容器之说,那什么是同步容器与并发容器呢?同步容器可以简单地理解为通过synchronized来实现同 ...

  3. 013-并发编程-java.util.concurrent.locks之-AbstractQueuedSynchronizer-用于构建锁和同步容器的框架、独占锁与共享锁的获取与释放

    一.概述 AbstractQueuedSynchronizer (简称AQS),位于java.util.concurrent.locks.AbstractQueuedSynchronizer包下, A ...

  4. Java并发机制(5)--同步容器与并发容器

    Java并发编程:同步容器整理自:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3933404.html1.同步容器出现原因 常用的ArrayList,Lin ...

  5. JAVA同步容器和并发容器

    同步容器类 同步容器类的创建 在早期的JDK中,有两种现成的实现,Vector和Hashtable,可以直接new对象获取: 在JDK1.2中,引入了同步封装类,可以由Collections.sync ...

  6. Java并发——同步容器与并发容器

    同步容器类 早期版本的JDK提供的同步容器类为Vector和Hashtable,JDK1.2 提供了Collections.synchronizedXxx等工程方法,将普通的容器继续包装.对每个共有方 ...

  7. Java 同步容器和并发容器

      同步容器(在并发下进行迭代的读和写时并不是线程安全的)   Vector.Stack.HashTable   Collections类的静态工厂方法创建的类(如Collections.synchr ...

  8. 【Java并发编程二】同步容器和并发容器

    一.同步容器 在Java中,同步容器包括两个部分,一个是vector和HashTable,查看vector.HashTable的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并 ...

  9. Java的同步容器和并发容器

    前言: 之前在介绍Java集合的时候说到,java提供的实现类很少是线程安全的.只有几个比较古老的类,比如Vector.Hashtable等是线程安全的,尤其是Hashtable,古老到连命名规范都没 ...

随机推荐

  1. [源码解析] TensorFlow 分布式之 ParameterServerStrategy V2

    [源码解析] TensorFlow 分布式之 ParameterServerStrategy V2 目录 [源码解析] TensorFlow 分布式之 ParameterServerStrategy ...

  2. 第一篇:实时网络日志分析器和交互式查看器--GoAccess安装

    介绍 GoAccess是一个开源的实时网络日志分析器和交互式查看器,可以在* nix 系统或通过浏览器的终端中运行. 它为需要动态可视化服务器报告的系统管理员提供快速且有价值的HTTP统计信息. 特征 ...

  3. Mac-Typora快捷键

    标题(大钢) command+(1-6)) 如:command+1,设置为一级标题 引用 快捷键:command+option+Q 或者:先">",后面直接加内容 二级引用: ...

  4. 虚拟机:ESX

    VMware ESXi 与ESX 产品之比较   VMware vSphere 5.0 以后版本,所有底层虚拟化产品都改为ESXi产品,本文主要比较了ESXi与ESX的各自特点,以便对大家是否要把现有 ...

  5. Vue路由的安装

    1.在Vue ui中插件中找到添加vue-router 2.安装以后,项目中的会自动完成配置. 3.在store中的index.js配置路由页面以及路径.

  6. dubbo是如何实现可扩展的?(二)

    牛逼的框架,看似复杂难懂,思路其实很清晰.---me 上篇文章,在整体扩展思路上进行了源码分析,比较粗糙,现在就某些点再详细梳理下. dubbo SPi的扩展,基于一类.三注解. 一类是Extensi ...

  7. docker服务部署、迁移与备份、dockerfile、私有仓库

    今日内容概要 服务部署 迁移与备份 dockerfile 私有仓库 内容详细 1.服务部署 # 装 mysql redis --->源码编译安装-->启 动 # 有了docker后,容器操 ...

  8. 打通web的三维国产引擎!老子云AMRT,够牛!

    AMRT(Auto Mobile Reality Technology)指的是自动化移动现实技术,它是老子云3D模型自动轻量化引擎及轻量化模型格式.模型展示框架.API/SDK的统称.3D研发技术其中 ...

  9. 你要的几个JS实用工具函数(持续更新)

    今天,我们来总结下我们平常使用的工具函数,希望对大家有用.1.封装fetch 源码: /** * 封装fetch函数,用Promise做回调 * @type {{get: (function(*=)) ...

  10. 基于slate构建文档编辑器

    基于slate构建文档编辑器 slate.js是一个完全可定制的框架,用于构建富文本编辑器,在这里我们使用slate.js构建专注于文档编辑的富文本编辑器. 描述 Github | Editor DE ...