集合同步


词汇解析

词汇解析

如何以线程安全的方式使用Java List?

A. 经典做法

在迭代开始之前,使用synchronized手动同步所有针对List的修改操作(增加、删除),保证迭代期间没有线程修改List。此方法需要明确各个方法的同步,考虑的因素很多,不建议使用。

B. Synchronized Wrappers包装器

使用java.util.Collections的工厂方法Collections.synchronizedXXX(collection)

其保护代理(protectionproxy)模式,仅在正确的条件下才会调用原始List;需要明确指出的是,必须保证使用迭代器访问List的时候要在同步代码块(synchronized block)中,否则有可能抛出ConcurrentModificationException异常,如下(3)例子中所示用法。

C. Concurrent Collections(并发集合)

1. copy-on-writecollections

使用CopyOnWriteArrayList,它是完全的线程安全的集合类,包括其迭代器(Iterator & listIterator)也都是完全的线程安全,因此迭代(iteration)期间,不需要同步代码块,也不会抛出任何ConcurrentModificationException异常。

特别注意:不要通过迭代器iterator自己的方法(比如add(), set(), remove()去修改集合CopyOnWriteArrayList中的元素,这会引起抛出UnsupportedOperationException异常。

2. Compare-And-Swap(CAS )collections

ConcurrentLinkedQueueConcurrentSkipListMap

3. using a special lock object collections

LinkedBlockingQueue

1. ArrayList 使用了Collections.synchronizedList(List<T> list)就一定是线程安全了吗?

答案:不是

2. ArrayList 返回的迭代器iterator不是同步(synchronized)的,因此不是线程安全的,如何解决?

答案:由于迭代器快速失效(fail-fast)的特性,必须保证迭代期间在同步代码块内部,避免抛出ConcurrentModificationException异常。

3. 性能对比

Concurrent Collections > CopyOnWriteArrayList> Synchronized Wrappers > synchronized

4. 如果想使用双向链表结构的List应该选择哪种集合类?

答案:首先不能选择包装器(Synchronized Wrappers)因为,包装器不能包装LinkedList双向链表结构的List,如果强转会抛出ClassCastException异常,因此只能选择ConcurrentLinkedQueue或者LinkedBlockingQueue等并发包中的集合类。

5. LinkedList

@See http://sudotutorials.com/tutorials/java/collections/java-linkedlist-class.html

6. 包装器实现方式

注:同步列表上的单个操作保证是原子操作,但如果要以一致的方式执行并发操作(multiple operations),则必须同步(synchronized)该操作。

public class CollectionsSynchronizedList {

public static void main(String[] args) {

List<String> list = Collections.synchronizedList( new LinkedList<>(Arrays.asList("a", "b", "c")));

System.out.println("thread-safe list: " + list);

// single operations are atomic:

list.add("d");

// While Iterating over synchronized list, you must synchronize

// on it to avoid non-deterministic behavior

// multiple operations have to be synchronized:

synchronized (list) {

// Must be in synchronized block

for (Iterator<String> it = list.iterator(); it.hasNext(); ) {

System.out.println("item: " + it.next());

}

}

}

}

7. 多线程操作List

publicclassArrayListSynchronization {

publicstaticvoidmain(String[] args) {

finalList<Integer> arrayList = new ArrayList<Integer>();

finalList<Integer> synchronizedArrayList;

// Let's make arrayList synchronized

synchronizedArrayList = Collections.synchronizedList(arrayList);

//Thread 1 will add elements in synchronizedArrayList

Thread t1 = new Thread(new Runnable() {

publicvoidrun() {

for(int i = 0; i <= 3; i++) {

synchronizedArrayList.add(i);

try{

Thread.sleep(100);

catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}, "thread-1");

t1.start();

//Thread 2 will iterate on synchronizedArrayList

Thread t2 = new Thread(new Runnable() {

publicvoidrun() {

Iterator<Integer> it = synchronizedArrayList.iterator();

synchronized(synchronizedArrayList) { //synchronization block

while(it.hasNext()) {

try{

Thread.sleep(100);

catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(it.next());

}

}

}

}, "thread-2");

t2.start();

}

}

1. 主线程启动thread-1thread-1synchronizedArrayList里添加0,(也就是 i=0),接着thread-1进入休眠sleep(100),与此同时。

2. 主线程启动thread-2thread-2得到synchronizedArrayList对象的锁,接着thread-2得到synchronizedArrayList的迭代器,然后thread-2进入while循环,接着thread-2进入sleep(100)。

3. 休眠时间结束后thread-1进入运行状态去执行synchronizedArrayList的add(i)方法,但由于synchronizedArrayList已被synchronized限制(你必须清楚的是只有返回的迭代器是非线程安全外,synchronizedArrayList其余的所有方法都是完全的线程安全的), 因此thread-1必须等待thread-2释放synchronizedArrayList对象的锁。

4. 一旦thread-2释放synchronizedArrayList对象的锁thread-1将 1 (也就是 i=1) 添加进synchronizedArrayList, 并进一步完成循环,这不会抛出ConcurrentModificationException异常。

原文链接:http://www.javamadesoeasy.com/2015/12/how-to-synchronize-arraylist-in-java-to.html

几乎所有的集合非线程安全的?

ArrayList, LinkedList, HashMap, HashSet, TreeMap, TreeSet, 等都不是同步的,事实上,java.util包内所有的集合中除了Vector 和Hashtable都是非线程安全的。为什么?-因为同步的代价太昂贵了,为了在单线程的应用中取得最大性能,几乎所有的集合都是非线程安全的。

Vector 和Hashtable 在Java历史早期就已经存在的,一开始它们就被设计为线程安全的,查看它们的源码时你会发现其方法都是同步方法。然而它们很快就暴露出多线程下性能非常糟糕的表现。众所周知,同步(synchronization)需要取得耗费时间监视的同步锁(locks),因而降低性能。

这就是为什么新的集合(List,Set,Map等)根本不提供并发控制以在单线程应用程序中提供最高性能的原因。

我们在测试两个类似集合Vector(线程安全) 和 ArrayList(非线程安全)得出以下结论: 在添加同当量的数据时,ArrayList花费的时间明显要比Vector要少。

多线程与集合类

为了数据安全,多线程环境中,对同一个集合类进行增删改操作时,如何选择方案?

1. 选择线程安全的集合类,比如Vector和Hashtable,不用附加同步代码。

2. 选择非线程安全的集合类,必须使用synchronized做同步处理。

3. 选择非线程安全的集合类,配合同步包装方法对集合进行包装,包装后的对象(List,Set,Map等)是线程安全的,如下所示例子。

List<String> list = Collections.synchronizedList(new LinkedList<>());

Map<String, Account> map = Collections.synchronizedMap(new HashMap<>());

集合类与同步包装器

java.util.Collections具有为不同类型的Java集合创建同步包装的方法:

1. <T> Collection<T> synchronizedCollection(Collection<T> c)

Returns a synchronized (thread-safe) collection backed by the specified collection.

2. <T> List<T> synchronizedList(List<T> list)

Returns a synchronized (thread-safe) list backed by the specified list.

3. <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

Returns a synchronized (thread-safe) map backed by the specified map.

4. <K,V> NavigableMap<K,V> synchronizedNavigableMap(NavigableMap<K,V> m)

(since Java 1.8)

Returns a synchronized (thread-safe) navigable map backed by the specified navigable map.

5. <T> NavigableSet<T> synchronizedNavigableSet(NavigableSet<T> s)

(since Java 1.8)

Returns a synchronized (thread-safe) navigable set backed by the specified navigable set.

6. <T> Set<T> synchronizedSet(Set<T> s)

Returns a synchronized (thread-safe) set backed by the specified set.

7. <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)

Returns a synchronized (thread-safe) sorted map backed by the specified sorted map.

8. <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)

Returns a synchronized (thread-safe) sorted set backed by the specified sorted set.

拓展阅读

http://www.codejava.net/java-core/collections/understanding-collections-and-thread-safety-in-java

https://www.geeksforgeeks.org/synchronization-arraylist-java/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值