7.同步类容器与并发类容器

博客概述

本篇博客主要是对同步类容器与并发类容器做demo讲解。

同步类容器

同步类容器是线程安全的,但在某些场景下需要加锁来保护复合操作。复合类操作如:迭代(反复访问元素,遍历完容器中的所有元素),跳转(根据指定的顺序找到当前元素的下一个元素),以及条件运算,这些复合操作在多线程并发的修改容器时,可能会表现出意外的行为,最经典的便是concurrentModificationException,原因是当容器迭代的过程中,被并发的修改了内容,这是由于早期迭代器设计的时候没有考虑并发修改的问题。
同步类容器,如古老的vector,hashtable。这些容器的同步功能其实都是有jdk的Collections.synchronizedXXXX等工厂方法创建的。其底层的机制无非就是用传统的sync关键字对每个公用的方法进行同步,使得每次只有一个线程访问容器。这显然满足不了高并发的需求。
关于抛出经典异常的原因:http://bbs.csdn.net/topics/370149418

抛出异常代码与容器同步工具

在这些古老容器的底层都是使用的sync关键字同步的,比如vector的get/add方法。

public static void main(String[] args) {
		//初始化火车票池并添加火车票:避免线程同步可采用Vector替代ArrayList  HashTable替代HashMap
		
		final Vector<String> tickets = new Vector<String>();
		
		//Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());

		for(int i = 1; i<= 1000; i++){
			tickets.add("火车票"+i);
		}
		
//		for (Iterator iterator = tickets.iterator(); iterator.hasNext();) {
//			String string = (String) iterator.next();
//			tickets.remove(20);
//		}
		
		for(int i = 1; i <=10; i ++){
			new Thread("线程"+i){
				public void run(){
					while(true){
						if(tickets.isEmpty()) break;
						System.out.println(Thread.currentThread().getName() + "---" + tickets.remove(0));
					}
				}
			}.start();
		}
	}

并发类容器

因为同步类容器不能满足互联网场景下的高并发,就有了专门应对并发的并发类容器。真正的多线程场景,就用并发类容器。可以把同步类容器忘掉了。但是对于那种需要共享,不允许并发的资源,用同步容器还是很好的。
jdk1.5以后提供了多种并发类容器来替代同步容器从而改善性能,同步类容器的性能都是串行化的,他们虽然实现了线程安全,但是严重降低了并发性,在多线程场景下严重降低了应用程序的吞吐量。并发类容器是专门针对并发设计的,使用concurrenthashmap来代替给与散列的传统的hashtable,而且在concurrenthashmap中,添加了一些常见的复合操作的支持。以及使用copyonwriteArrayList代替vector,并发的copyonwritearrayset,以及并发的queue,concurrentlinkedqueue和linkedblockingqueue,前者是高性能的队列后者是阻塞形式的队列,具体的queue的实现还有很多,例如ArrayBlockingQueue,PriorityBlockingQueue,synchronousQueue。

concurrentMap接口

在这里插入图片描述
两种实现方式都支持高并发下的线程安全。
第一个类似hashmap,hashtable
第二个类似treemap(可以排序)
早期用hashtable做线程安全,锁的粒度很大,整个都锁起来。
当t1对hashtable写操作(insert,update,delete)的时候,t2也来了,但是它只能等待。
性能不是很好,这种方式很传统。
对于concurrenthashmap:
并发hashmap分了16个段(segment的概念),理解为他把一个hashtable分为16个小的段,在每一段上加锁,每一个段上都存数据。每个段可以理解为一个小的hashtable。然后线程访问这段上的数据。
这样的话假设访问的数据在不同的段上,就支持16个并发。
设计思想:减小锁的粒度。减少多线程锁竞争问题。
如果想操作同一段,比如都想操作第一段上的A数据,那得等着。因为一段就是一个小的hashtable。
使用方法跟一般的map没有区别:


import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class UseConcurrentMap {

	public static void main(String[] args) {
		ConcurrentHashMap<String, Object> chm = new ConcurrentHashMap<String, Object>();
		chm.put("k1", "v1");
		chm.put("k2", "v2");
		chm.put("k3", "v3");
		chm.putIfAbsent("k4", "vvvv");
		//System.out.println(chm.get("k2"));
		//System.out.println(chm.size());
		
		for(Map.Entry<String, Object> me : chm.entrySet()){
			System.out.println("key:" + me.getKey() + ",value:" + me.getValue());
		}
	}
}

Copy-On-Write容器

在这里插入图片描述
也是基于设计的容器,对同步类容器的优化。并不是什么新的技术,而是一些设计思想的实现。
实现了读写分离的思想。这种思想的前提是一瞬间插入的数据,不会马上读取,插入的同时,可以读取之前旧容器里面的任何数据。复制出来的容器被写,在写操作过程中,可以让其他很多线程并发读。写完了再把引用指向新的容器。当然这个容器使用的前提是,你插入数据的那一瞬间,你查询的数据不是你插入的数据,在这个逻辑里,这个cow容器是好使的。 **适用的场景:这种适合读的操作多的场景。如果写多的场景,还不如用加锁的形式去实现。**需要注意的是,底层的add加了reentrantlock锁,不会出现写数据的不一致的问题。reentrantlock:当有一个线程正在写的时候,其他线程需要等待。重入锁的性能高于读写锁(读读共享,有写互斥)。

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

public class UseCopyOnWrite {

	public static void main(String[] args) {
		
		CopyOnWriteArrayList<String> cwal = new CopyOnWriteArrayList<String>();
		CopyOnWriteArraySet<String> cwas = new CopyOnWriteArraySet<String>();
		
		
	}
}

总结

cow和并发map,只是提高了性能。但是并不能处理所有的并发问题。比如concurrenthashmap只支持16个段。COW再高的并发读都能扛得住,就是写得加锁(写操作不能多)。具体场景尝试具体容器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值