1.引言

  在多线程的环境中,如果想要使用容器类,就需要注意所使用的容器类是否是线程安全的。在最早开始,人们一般都在使用同步容器(Vector,HashTable),其基本的原理,就是针对容器的每一个操作,都添加synchronized来进行同步,此种方式尽管简单,但是其性能是非常地下的,所以现在已经不怎么使用了。人们普遍会使用并发的容器,在JDK1.5之后,针对基于散列的Map,提供了新的ConcurrentHashMap,针对迭代需求的list,提供了CopyOnWriteList.

2.ConcurrentHashMap
  ConcurrentHashMap使用了一种分段锁的策略,使得map可以被多个读写线程并行的访问。基本可以认为是将map的key值范围分为多个段,这样多个线程访问的时候,他们需要访问的key值在不同的段,所以可以互相不干扰,
使用不同的锁对象来进行并发操作。
  ConcurrentHashMap在使用迭代器遍历的时候,不会报ConcurrentModificationException,提供“弱一致性”。在遍历迭代的时候,也会反应出在迭代器创建之后的数据修改。

应用场景
针对一般的有并发需求的map,都应该使用ConcurrentHashMap. 它的性能优于Hashtable和synchronizedMap。
缺点
不是强一致性 
  由于是采用的分段锁策略,所以一些数据不能保证强一致性。比如针对容器的size方法,由于线程A只是获得了自己的分段锁,它不能保证其他线程对容器的修改,所以此时线程A可能使用size,会得到不稳定数据。这种情况下,是对同步性能的一些折衷。如果业务需求必须满足强一致性,才会需要对整个Map进行锁操作。并发容器的弱一致性的概念背景,是在高并发情况下,容器的size和isEmpty之类的方法,用处不大,所以可以忍受数据不一致性。

3.CopyOnWrite容器
  在JDK1.5之后,java.util.concurrent引入了两个CopyOnWrite容器,分别是CopyOnWriteArrayList, CopyOnWriteArraySet.
  顾名思义,CopyOnWrite就是在write操作之前,对集合进行Copy,针对容器的任意改操作(add,set,remove之类),都是在容器的副本上进行的。并且在修改完之后,将原容器的引用指向修改后的副本。
如果线程A得到容器list1的iterator之后,线程B对容器list1加入了新的元素,由于线程A获得list1的iterator时候在线程B对list1进行修改前,所以线程A是看不到线程B对list1进行的任何修改。
具体到源码,看一下add操作

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

可以发现,写操作是会有个锁lock.lock(),这保证了多线程写操作之间的同步。之后使用Arrays.copyOf来进行数组拷贝,在修改完成后,setArray(newElements)将原来的数组引用指向新的数组。
  应用场景
经常用在读多写少的场景,比如EventListener的添加,网站的category列表等偶尔修改,但是需要大量读取的情景。
  缺点
1.数据一致性的问题。  
  因为读操作没有用到并发控制,所以可能某个线程读到的数据不是实时数据。
2.内存占用问题。
  因为写操作会进行数据拷贝,并且旧有的数据引用也可能被其他线程占有一段时间,这样针对数据比较大的情况,可能会占用相当大的内存。并且由于每次写操作都会占用额外的内存,最后进行的GC时间也可能相应的增加。

4.HashSet

HashSet内部是用的HashMap,只用了HashMap的key。

同步集合
    传统集合类在并发访问时的问题说明:死锁死循环
    传统方式下用Collections工具类提供的synchronizedCollection方法来获得同步集合,分析该方法的实现源码
    Java5中提供了如下一些同步集合类:
       通过看java.util.concurrent包下的介绍可以知道有哪些并发集合
        ConcurrentHashMap
        CopyOnWriteArrayList
        CopyOnWriteArraySet
    传统方式下的Conllection在迭代结合时,不允许对集合进行修改。
        根据AbstractList的checkForComodification方法的源码,分析产生ConcurrentModificationException异常的原因。
public class User implements Cloneable{
private String name;
private int age; public User(String name, int age) {
this.name = name;
this.age = age;
}
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(!(obj instanceof User)) {
return false;
}
User user = (User)obj;
//if(this.name==user.name && this.age==user.age)
if(this.name.equals(user.name)
&& this.age==user.age) {
return true;
}
else {
return false;
}
}
public int hashCode() {
return name.hashCode() + age;
} public String toString() {
return "{name:'" + name + "',age:" + age + "}";
}
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {}
return object;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
}

JAVA多线程学习十六 - 同步集合类的应用的更多相关文章

  1. “全栈2019”Java多线程第十六章:同步synchronized关键字详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  2. “全栈2019”Java多线程第二十六章:同步方法生产者与消费者线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  3. Java多线程学习(六)Lock锁的使用

    系列文章传送门: Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多 ...

  4. JAVA多线程学习十二 - Semaphere同步工具

    java 中Semaphere可类比操作系统信号量,硬件资源如IO.内存.磁盘等都是有固定量的,多个程序需要竞争这些资源,没有资源就需要被挂起. 一.类和方法摘要 构造函数: public Semap ...

  5. JAVA多线程学习2--线程同步

    一.线程同步介绍 同步:就是协同步调,按照预定的先后顺序执行.比如:你说完我再说. 线程同步:访问同一个共享资源的时候多个线程能够保证数据的安全性.一致性. 二.JAVA中实现线程同步的方法 实现进程 ...

  6. JAVA多线程学习十五 - 阻塞队列应用

    一.类相关属性 接口BlockingQueue<E>定义: public interface BlockingQueue<E> extends Queue<E> { ...

  7. JAVA多线程提高十四:同步工具Exchanger

    Exchanger可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象.Exchanger 可能被视 ...

  8. Java开发学习(十六)----AOP切入点表达式及五种通知类型解析

    一.AOP切入点表达式 对于AOP中切入点表达式,总共有三个大的方面,分别是语法格式.通配符和书写技巧. 1.1 语法格式 首先我们先要明确两个概念: 切入点:要进行增强的方法 切入点表达式:要进行增 ...

  9. Java多线程学习总结--线程同步(2)

    线程同步是为了让多个线程在共享数据时,保持数据的一致性.举个例子,有两个人同时取钱,假设用户账户余额是1000,第一个用户取钱800,在第一个用户取钱的同时,第二个用户取钱600.银行规定,用户不允许 ...

随机推荐

  1. Linux_Cornd任务调度

    Crond任务调度 进行定时任务的设置 概述 任务调度:是指系统在某个时间执行特定的命令或程序 作用:避免重复工作 基本语法 crontab [选项] 选项 功能 -e 编辑crontab定时任务 - ...

  2. 编写Java程序,使用JDBC连接SQL Server数据库

    返回本章节 返回作业目录 需求说明: 使用JDBC连接SQL Server数据库 SQL Server数据库位于192.168.2.101. 所需连接的数据库为eshop_db,用户名为test,密码 ...

  3. python 脚本或者flask 注册成为windows服务

    分享下脚本注册成为win服务 这个借鉴了其他大佬得 具体是谁忘记了有机会推荐 今天也是找了之前得改出来得 首先安装三个模块 最后一个是定时任务如果不需要可以不装 pip install psutil ...

  4. java并发系列——底层CPU

    java并发有诸多难点,实际上并非java语言本身的问题,本质上说一部分是因为并发操作本身的问题,另外一部分是因为计算机体系结构带来的.为了更好地理解java并发过程中的问题,我们应该对CPU有一些基 ...

  5. Api接口测试总是似懂非懂,只因这个原理没搞清楚

    ​ 前言 掌握了http协议,就掌握了接口测试 笔者在网络上看过不少接口测试教程,一上来就开始讲怎么操作工具,而不告诉读者为什么要这么操作.读者可能照猫画虎成功了,也可能操作失败了但不知为何出错. 因 ...

  6. Python中单下划线与双下划线用法总结

    看mentor的脚本时,遇到self._item.callspec.getparam('')语句,理解起来比较困难,找到一篇文章,记录的比较详细,特别记录一下,以备复习. 附链接地址:http://w ...

  7. MySQL约束和数据类型

    约束条件 约束条件就是在给字段加一些约束,使该字段存储的值更加符合我们的预期. 常用约束条件如下: UNSIGNED :无符号,值从0开始,无负数 ZEROFILL:零填充,当数据的显示长度不够的时候 ...

  8. [ vue ] xxxProject项目杂记

    2020.4.9 加入eCharts 2020.4.8 完成article的显示,其间碰到全局路由守卫写的有错误,导致跳转报错.已修复. 加入keep-alive功能,缓存视图数据 疑问:如果在全局组 ...

  9. PPT2010制作翻牌动画

    原文: https://www.toutiao.com/i6492653280676545037/ 新建一张空白幻灯片 选择"插入"选项卡,"插入"功能组,&q ...

  10. 一文搞定 Windows Terminal 设置与 zsh 安装 (非WSL)

    为 Windows Terminal 添加标签页 添加 Anaconda 标签页 在settings.json文件中的list列表中添加设置项: { // Make changes here to t ...