词汇解析
如何以线程安全的方式使用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
ConcurrentLinkedQueue、ConcurrentSkipListMap
3. using a special lock object collections
LinkedBlockingQueue
1. ArrayList 使用了Collections.synchronizedList(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
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
System.out.println("item: " + it.next());
}
}
}
}
7. 多线程操作List
publicclassArrayListSynchronization {
publicstaticvoidmain(String[] args) {
finalList
finalList
// 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
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-1,thread-1往synchronizedArrayList里添加0,(也就是 i=0),接着thread-1进入休眠sleep(100),与此同时。
2. 主线程启动thread-2,thread-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
Map
集合类与同步包装器
java.util.Collections具有为不同类型的Java集合创建同步包装的方法:
1.
Returns a synchronized (thread-safe) collection backed by the specified collection.
2.
Returns a synchronized (thread-safe) list backed by the specified list.
3.
Returns a synchronized (thread-safe) map backed by the specified map.
4.
(since Java 1.8)
Returns a synchronized (thread-safe) navigable map backed by the specified navigable map.
5.
(since Java 1.8)
Returns a synchronized (thread-safe) navigable set backed by the specified navigable set.
6.
Returns a synchronized (thread-safe) set backed by the specified set.
7.
Returns a synchronized (thread-safe) sorted map backed by the specified sorted map.
8.
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/