脑图

线程内安全的容器 线程安全的容器类_线程内安全的容器

概述

之前讲了一些常用的线程不安全的集合容器(ArrayList、HashMap、HashSet),如果有多个线程并发访问这些集合时就会出现线程不安全的问题。 当我们在使用这些容器时,需要我们自己来处理线程安全的问题。 使用起来相对会有些不便,而Java在这方面提供了相应的同步容器,我们可以在多线程情况下可以结合实际场景考虑使用这些同步容器。

同步容器

线程内安全的容器 线程安全的容器类_并发容器_02

集合接口下的同步容器实现类 

  • Vector的方法都是由synchronized关键字保护

线程内安全的容器 线程安全的容器类_线程内安全的容器_03

ctrl + o,方法左侧 带有

线程内安全的容器 线程安全的容器类_线程内安全的容器_04

,就可以看出是个同步方法。 

  • Stack继承了Vector,并且提供了栈操作(先进后出)
  • Hashtable也是由synchronized关键字保护

线程内安全的容器 线程安全的容器类_并发容器_05

 

 Vector (线程安全性比ArrayList好一些,但并非绝对线程安全)

线程内安全的容器 线程安全的容器类_线程内安全的容器_06

运行结果:

线程内安全的容器 线程安全的容器类_迭代器_07

 

 这种情况下 ,多线程 计算结果正确

同步容器 线程不安全的场景

同步容器也并不一定是绝对线程安全的,例如有两个线程,线程A根据size的值循环执行remove操作,而线程B根据size的值循环执行执行get操作。它们都需要调用size获取容器大小,当循环到最后一个元素时,若线程A先remove了线程B需要get的元素,那么就会报越界错误

Vector中的方法都进行了同步处理,那么一定就是线程安全的,事实上这可不一定 。来演示下

线程内安全的容器 线程安全的容器类_线程内安全的容器_08

运行结果: java.lang.ArrayIndexOutOfBoundsException

线程内安全的容器 线程安全的容器类_线程内安全的容器_09

 

我们来分析一下:

Vector是线程安全的,为什么还会报这个错?对于Vector,虽然能保证每一个时刻只能有一个线程访问它,但是不排除这种可能:

当某个线程在某个时刻执行这句时:

for(int i=0;i<vector.size();i++){
   vector.get(i);
}

假若此时vector的size方法返回的是10,i的值为9

然后另外一个线程执行了这句:

for(int i=0;i<vector.size();i++){
	vector.remove(i);
}

将下标为9的元素删除了, 那么通过get方法访问下标为9的元素肯定就会出问题了。

因此为了保证线程安全,必须在方法调用端做额外的同步措施

线程内安全的容器 线程安全的容器类_并发容器_10

其他注意事项 

当我们使用foreach循环或迭代器去遍历元素的同时又执行删除操作的话,即便在单线程下也会报并发修改异常

线程内安全的容器 线程安全的容器类_线程内安全的容器_11

所以在foreach循环或迭代器遍历的过程中不能做删除操作,若需遍历的同时进行删除操作的话尽量使用for循环。实在要使用foreach循环或迭代器的话应该先标记要删除元素的下标,然后最后再统一删除. 如果使用JDK8,可以使用函数式编程

线程内安全的容器 线程安全的容器类_并发容器_12

 


Hashtable

线程不安全的HashMap

运行结果:

线程内安全的容器 线程安全的容器类_迭代器_13

 


Collections.synchronizedXXX方法所创建的同步容器

Collections类中提供了多个synchronizedXxx方法, 该方法返回指定集合对象对应的同步对象,从而可以解决多线程并发访问集合时的线程安全问题 

线程内安全的容器 线程安全的容器类_线程内安全的容器_14

Collections.synchronizedList

运行结果: 线程安全

线程内安全的容器 线程安全的容器类_并发容器_15

 


Collections.synchronizedMap 

线程内安全的容器 线程安全的容器类_迭代器_16

运行结果: 线程安全

线程内安全的容器 线程安全的容器类_迭代器_17


Collections.synchronizedSet 

 

线程内安全的容器 线程安全的容器类_线程内安全的容器_18

运行结果: 线程安全

线程内安全的容器 线程安全的容器类_并发容器_19


小结

同步容器是通过synchronized来实现同步的,所以性能较差。而且同步容器也并不是绝对线程安全的,在一些特殊情况下也会出现线程不安全的行为。那么有没有更好的方式代替同步容器呢?----> 那就是**并发容器,有了并发容器后同步容器的使用也越来越少的,大部分都会优先使用并发容器(J.U.C)**

 总之一句话,优先使用并发容器提供的集合,而不是使用加了锁的同步容器中的集合