Collections.synchronizedXXX学习

      今天又学到新东西啦~~~ 活到老学到老 ~~~ 加油ヾ(◍°∇°◍)ノ゙
      特来总结一下,怕忘了 (; ̄ェ ̄)
源代码是这样的:

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值