同步类容器与并发类容器

一、同步类容器

同步类容器都是线程安全的,但在某些场景下可能需要加锁来保护复合操作。复合类操作:迭代(反复访问元素,遍历完容器中所有的元素)、跳转(根据指定的顺序找到当前元素的下一个元素)、以及条件运算。这些复合操作在多线程并发地修改容器时,可能会表现出意外的行为,最经典的便是ConcurrentModificationException,原因是当容器迭代的过程中,被并发的修改了内容,这是由于早期迭代器设计的时候并没有考虑并发修改的问题。

import java.util.Iterator;
import java.util.Vector;

public class Tickets {

	public static void main(String[] args) {
		//初始化火车票池并添加火车票:避免线程同步可采用Vector替代ArrayList
		final Vector<String> tickets = new Vector<>();
		
		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);
		}
	}
}

执行上述代码,其输出结果为:

Exception in thread "main" java.util.ConcurrentModificationException

在迭代的过程中同时对该同步容器tickets进行了修改,因此出现了上述异常。 

import java.util.Vector;

public class Tickets {

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

		for(int i = 1; i<= 1000; i++){
			tickets.add("火车票"+i);
		}
		
		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();
		}
	}
}

上面一段代码是对于同步类容器开启10个线程去删除元素并不是一个复合型操作,不会存在前面的并发修改问题。 

同步类容器:如古老的Vector、HashTable。这些容器的同步功能其实都是由JDK的Collections.synchronized***等工厂方法去创建实现的。其底层的机制无非就是用传统的synchronized关键字对每个公用的方法都进行同步,使得每次只能有一个线程访问容器的状态。这很明显不满足我们今天互联网时代高并发的需求,在保证线程安全的同时,也必须有足够好的性能。

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class Demo {

	public static void main(String[] args) {
		Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
	}
}

HashMap本身不是线程安全的,但是被Collections.synchronizedMap包裹以后产生的新map是线程安全的。

二、并发类容器

JDK5.0以后提供了多种并发类容器来替代同步类容器从而改善性能。同步类容器的状态都是串行化,它们虽然实现了线程安全,但是严重降低了并发性,在多线程环境时,严重降低了应用程序的吞吐量。

并发类容器是专门针对并发设计的,例如使用ConcurrentHashMap来代替给予散列的传统的HashTable,而且在ConcurrentHashMap中,添加了一些常见复合操作的支持。并发类容器还提供了CopyOnWriteArrayList代替Vector(ArrayList),并发的CopyOnWriteArraySet,以及并发的队列ConcurrentLinkedQueue类和Queue接口(5种实现),前者是高性能的队列,后者是阻塞式的队列。

三、ConcurrentHashMap并发容器

3.1  ConcurrentHashMap的介绍

ConcurrentHashMap并发容器可以理解为同步容器hashTable的升级版

JDK1.7:ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的HashTable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。把一个整体分成16个段(Segment),也就是最高支持16个线程的并发需改操作,这也是在多线程场景时减小锁的粒度从而降低锁竞争的一种方案。并且ConcurrentHashMap代码中大多共享变量使用的是volatile关键字声明,目的是第一时间获取修改的内容,性能非常好。

关于并发度值,它是ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能减少竞争。

JDK8:在JDK8后,ConcurrentHashMap摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现:利用CAS算法。同时加入了更多的辅助变量来提高并发度。

如果原来Node节点中没有元素,那么多线程都可以去进行比较和交换的;如果是需要形成链表的话是单独对数组加synronized,减少synronized锁的对象,所以它的这种方式我们站在并发编程的角度来讲,它的这个方式相对来说效率会更高一点。对于hash冲突不高的情况下更多的时候是采用一种无锁化的机制,它的并发度就非常高;如果发生hash冲突的话,则锁的每一个数组的元素,所以这种方式也没有一个确定的并发度。在高并发场景下肯定比JDK1.7要好。

3.2  ConcurrentHashMap的使用

ConcurrentHashMap中代码中的使用和HashMap一模一样

import java.util.concurrent.ConcurrentHashMap;

public class Demo {
	public static void main(String[] args) {
		ConcurrentHashMap<String,Object> map =new ConcurrentHashMap<>();
		map.put("k1","v1");
		map.put("k2","v2");
		map.put("k3","v3");
		// 调用putIfAbsent方法时,如果集合中已经存在该key,则不进行插入操作
		map.putIfAbsent("k3","v33");
		System.out.println(map);
	}
}

执行上述代码,其输出结果为:

{k1=v1, k2=v2, k3=v3}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值