java 集合读写同步_JAVA多线程学习十六 - 同步集合类的应用

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操作

48304ba5e6f9fe08f3fa1abda7d326ab.png

/**

* 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();

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

可以发现,写操作是会有个锁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异常的原因。

48304ba5e6f9fe08f3fa1abda7d326ab.png

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;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值