今天又学到新东西啦~~~ 活到老学到老 ~~~ 加油ヾ(◍°∇°◍)ノ゙
特来总结一下,怕忘了 (; ̄ェ ̄)
源代码是这样的:
List<Person> allList = Collections.synchronizedList(new ArrayList());
synchronized (allList) {
allList.add(o);
}
要了解 Collections.synchronizedList(new ArrayList()),还需要了解下面这些内容。
1.集合大多数都不是线程安全的
LinkedList、HashMap、TreeMap、HashSet、TreeSet都不是线程安全的。实际上,java.util包中的所有集合类(Vector和Hashtable除外)都不是线程安全的。只有两个类是线程安全的:Vector和Hashtable。为什么呢?
原因就在于线程安全导致性能降低。
Vector和Hashtable是Java历史早期存在的两个集合,它们从一开始就设计用于线程安全(如果你有机会查看它们的源代码,你会发现它们的方法都是同步的!)。但是,它们很快就会在多线程程序中暴露出糟糕的性能。同步需要锁,这些锁总是需要时间来监控,这会降低性能。
这就是新集合(List,Set,Map等)根本不提供并发控制,这样可以在单线程应用程序中提供最大性能的原因。
下面代码测试一下在相同条件下,Vector和ArrayList运行的时间(毫秒数):
package com;
import java.util.ArrayList;
import java.util.Vector;
public class CollectionTest {
public static void vectorTest(){
Vector vector = new Vector<>();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
vector.add(i);
}
long end = System.currentTimeMillis();
System.out.println("Vector消耗:"+(end-start));
}
public static void arrayListTest(){
ArrayList arrayList = new ArrayList<>();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
arrayList.add(i);
}
long end = System.currentTimeMillis();
System.out.println("ArraList消耗:"+(end-start));
}
public static void main(String[] args) {
vectorTest();
arrayListTest();
}
}
运行结果:(电脑卡)
Vector消耗:67767
ArraList消耗:61024
虽然电脑卡了点,没有我看的那篇差别大,但也可以看到,在使用相当多的元素的时候,ArrayList的速度要比Vector的速度快。
2.失败快速迭代器
使用集合时,您还需要了解有关其迭代器的并发策略:失败快速迭代器。
List<Integer> asList = Arrays.asList(1,2,3,4,5);
Iterator<Integer> iterator = asList.iterator();
while (iterator.hasNext()) {
Integer integer = (Integer) iterator.next();
System.out.println(integer);
}
在这里,我们使用集合的迭代器来遍历集合中的元素。想象一下,listNames在两个线程之间共享:执行迭代的当前线程和另一个线程。现在假设第二个线程正在修改集合(添加或删除元素),而第一个线程仍在迭代元素。你能猜出会发生什么吗?
第一个线程中的迭代代码抛出ConcurrentModificationException并立即失败,因此称为“ fail-fast iterators ”。
为什么迭代器失败的速度如此之快?这是因为在一个集合被另一个线程修改时迭代它是非常危险的:在获得迭代器之后集合可能有更多,更少或没有元素,因此导致意外行为和不一致的结果。这应该尽早避免,因此迭代器必须抛出异常来停止当前线程的执行。
以下测试程序模仿抛出ConcurrentModificationException的情况:
package com;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class IteratorTest {
private List<Integer> list = new ArrayList<>();
public void listAdd(){
for (int i = 0; i < 100; i++) {
list.add(i);
}
}
public void listUpdate(){
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 100; i < 200; i++) {
list.add(i);
}
}
});
thread1.start();
}
public void listIterator(){
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = (Integer) iterator.next();
System.out.println(integer);
}
}
});
thread2.start();
}
public static void main(String[] args) {
IteratorTest iteratorTest = new IteratorTest();
iteratorTest.listAdd();
iteratorTest.listIterator();
iteratorTest.listUpdate();
}
}
运行结果:
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at com.IteratorTest$2.run(IteratorTest.java:37)
at java.lang.Thread.run(Unknown Source)
0
thread1正在迭代列表,而thread2则向集合中添加更多元素。这会导致抛出ConcurrentModificationException。
3.同步包装
问题来了,集合基本上都不是线程安全的,但是还是会在多线程中用到集合,哪有改怎么办咧?
答:使用synchronized块来保护代码。
Java Collections Framework提供了用于创建线程安全集合的工厂方法。这些方法的形式如下:
Collections.synchronizedXXX(集合)
这些工厂方法包装指定的集合并返回线程安全的实现。这里,XXX可以是Collection,List,Map,Set,SortedMap和SortedSet实现。
例如,以下代码创建一个线程安全的列表集合:
List<Person> allList = Collections.synchronizedList(new ArrayList());
这就是我一开始遇到的问题啦~~~
还可以创建Map,等等。
Map<Integer, String> unsafeMap = new HashMap<>();
Map<Integer, String> safeMap = Collections.synchronizedMap(unsafeMap);
这些工厂方法将指定的集合进行包装,所有方法都是同步的,以提供线程安全性,因此称为“ 同步包装器 ”。实际上,synchronized集合将所有工作委托给包装集合。
其实他就是在集合的方法外面加了同步锁,你也可以自己加,不用这个方法。
当使用同步集合的迭代器时,我们应该使用synchronized块来保护迭代代码,因为迭代器本身不是线程安全的。代码示例:
List<Person> allList = Collections.synchronizedList(new ArrayList());
Iterator<Integer> iterator = allList .iterator();
while (iterator.hasNext()) {
Integer integer = (Integer) iterator.next();
System.out.println(integer);
}
虽然allList 是线程安全的,但是迭代器不是,所以我们还需要添加synchronized块。
List<Person> allList = Collections.synchronizedList(new ArrayList());
synchronized(allList ){
Iterator<Integer> iterator = allList .iterator();
while (iterator.hasNext()) {
Integer integer = (Integer) iterator.next();
System.out.println(integer);
}
}
还有一点问题,同步集合的迭代器是快速失败的。
使用Collection实用程序类’factory methods synchronizedXXX(collection)创建同步集合。它们是线程安全的但性能很差。
并发集合
同步集合的缺点是它们的同步机制使用集合对象本身作为锁对象。这意味着当一个线程迭代集合中的元素时,所有其他集合的方法都会阻塞,导致其他线程不得不等待。在第一个线程释放锁之前,其他线程无法对集合执行其他操作。这会导致开销并降低性能。
这就是Java 5(及更高版本)引入并发集合的原因,这些集合提供了比同步包装器更好的性能。并发集合类在java.util.concurrent包中进行组织。它们根据其线程安全机制分为3组。
*第一组是写时复制集合:这种线程安全集合将值存储在不可变数组中; 对集合值的任何更改都会导致创建一个新数组以反映新值。这些集合适用于读取操作极大地主导写入操作的情况。
这种实现有两种:CopyOnWriteArrayList和CopyOnWriteArraySet。
请注意,写时复制集合具有不会抛出ConcurrentModificationException的快照迭代器。由于这些集合由不可变数组支持,因此迭代器可以读取其中一个数组中的值(但从不修改它们),而不会被另一个线程更改。
*第二组是Compare-And-Swap或CAS集合:该组中的集合使用称为Compare-And-Swap(CAS)的算法实现线程安全,可以理解如下:
要执行变量的计算和更新值,它会生成变量的本地副本并执行计算而不会获得独占访问权限。当它准备好更新变量时,它会将变量的值与其开始时的值进行比较,如果它们相同,则使用新值进行更新。
如果它们不相同,则该变量必须已由另一个线程更改。在这种情况下,CAS线程可以使用新值再次尝试整个计算,或者放弃或继续。使用CAS的集合包括ConcurrentLinkedQueue和ConcurrentSkipListMap。
*第三组是使用特殊锁对象(java.util.concurrent.lock.Lock)的并发集合:此机制比传统同步更灵活。
在同步代码中,在执行代码块或方法时保持对象锁定,保持Lock直到调用unlock()方法。一些实现利用这种机制将集合划分为可以单独锁定的部分,从而提高了并发性。例如,LinkedBlockingQueue具有队列头部和尾部的单独锁,因此可以并行添加和删除元素。
使用此锁的其他集合包括ConcurrentHashMap和BlockingQueue的大多数实现。
该组中的集合也具有弱一致的迭代器,并且不会抛出ConcurrentModificationException。
转载链接:
[1]:https://www.codejava.net/java-core/collections/understanding-collections-and-thread-safety-in-java