JAVA集合同步

2018年6月19日12:06:45 发表评论 345

集合同步

  • 词汇解析

protection​​ proxy

保护代理

synchronized block

同步代码块

iteration

迭代

iterator

迭代器

fail-fast

快速失效

Concurrent Collections

并发集合

 

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

  • 经典做法

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

  • Synchronized Wrappers包装器

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

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

  • Concurrent Collections(并发集合)

  • copy-on-write collections

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

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

  • Compare-And-SwapCAS collections

ConcurrentLinkedQueueConcurrentSkipListMap

  • using a special lock object collections

LinkedBlockingQueue

 

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

答案:不是

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

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

  • 性能对比

Concurrent Collections​​ >​​ CopyOnWriteArrayList​​ >​​ Synchronized Wrappers​​ >​​ synchronized

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

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

  • LinkedList

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

 

  • 包装器实现方式

注:同步列表上的单个操作保证是原子操作,但如果要以一致的方式执行并发操作(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());

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​​​ }

}

 

  • 多线程操作List

public​​ class​​ ArrayListSynchronization {

  ​​ public​​ static​​ void​​ main(String[]​​ args) {

         ​​ final​​ List<Integer>​​ arrayList​​ =​​ new​​ ArrayList<Integer>();

         ​​ final​​ List<Integer>​​ synchronizedArrayList;

         ​​ // Let's make arrayList synchronized

         ​​ synchronizedArrayList​​ = Collections.synchronizedList(arrayList);

         ​​ //Thread 1 will add elements in​​ synchronizedArrayList

         ​​ Thread​​ t1​​ =​​ new​​ Thread(new​​ Runnable() {

                ​​ public​​ void​​ run() {

                      ​​ 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() {

                ​​ public​​ void​​ run() {

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

  ​​ }

}

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

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

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

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

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

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

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

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

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

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

 

多线程与集合类

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

  • 选择线程安全的集合类,比如VectorHashtable,不用附加同步代码。

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

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

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

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

集合类与同步包装器

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

  • <T> Collection<T> synchronizedCollection(Collection<T> c)

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

  • <T> List<T> synchronizedList(List<T> list)

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

  • <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

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

  • <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.

  • <T> NavigableSet<T> synchronizedNavigableSet(NavigableSet<T> s)

(since Java 1.8)

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

  • <T> Set<T> synchronizedSet(Set<T> s)

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

  • <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)

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

  • <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/

 

weinxin
微信公众号
分享IT信息技术、北海生活的网站。提供北海本地化的信息技术服务。
连线北海

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: