【转载】Java容器的线程安全
转自:http://blog.csdn.net/huilangeliuxin/article/details/12615507
同步容器类
同步容器类包括Vector和Hashtable(二者是早期JDK的一部分),还包括JDK1.2中添加的一些相似的类。同步容器类实现线程安全的方式是:将状态封闭起来,并对每个公有方法进行同步,使得每次只有一个线程能访问容器状态。这里解释一下所谓“状态”指的就是成员变量,“封装起来”即将它们设不private,但是通过公有的方法外界仍然可以访问修改类的私有成员,所以要用synchronized将公有方法进行同步,使得每次只有一个线程能访问容器状态。在多线程环境下调用同步容器类自带的所有方法时,实际上都是在串行执行,所以这严重降低并发性和吞吐量。
像List、Set、Map这些原本不是同步容器类,也可以通过Collections.synchronizedXXX工厂方法将其变为同步容器类,即对其公有方法进行同步。
- List<Student> students=Collections.synchronizedList(new ArrayList<Student>());

同步容器类的问题
容器上常见的操作包括:迭代访问、跳转(根据指定顺序找到当前元素的下一个元素)、条件运算(先检查再操作Check-And-Act,比如若没有则添加)。
- public static Object getLast(Vector vec){
- int lastIndex=vec.size()-1;
- return vec.get(lastIndex);
- }
- public static Object deleteLast(Vector vec){
- int lastIndex=vec.size()-1;
- return vec.remove(lastIndex);
- }

大线程的环境下,getLast()函数中第一行代码之后第二行代码之前如果执行了deleteLast(),那么getLast()继续执行第二行就会抛出ArrayIndexOutOfBoundException。所以要把getLast()和deleteLast()都变成原子操作:
- public static Object getLast(Vector vec){
- synchronized(vec){
- int lastIndex=vec.size()-1;
- return vec.get(lastIndex);
- }
- }
- public static Object deleteLast(Vector vec){
- synchronized(vec){
- int lastIndex=vec.size()-1;
- return vec.remove(lastIndex);
- }
- }

又比如容器上的迭代操作:
在size()之后get()之前,其他线程可能删除了vec中的元素,同样会导致抛出ArrayIndexOutOfBoundException。但这并不意味着Vector不是线程安全的,Vector的状态仍然是有效的,而抛出的异常也与其规范保持一致。
正确的做法是在迭代之前对vector加锁:
- synchronized(vec){
- for(int i=0;i<vec.size();i++)
- doSomething(vec.get(i));
- }

迭代器与ConcurrentModificationException
使用for或for-each循环对容器进行迭代时,javac内部都会转换成使用Iterator。在对同步容器类进行迭代时如果发现元素个数发生变化,那么hasNext和next将抛出ConcurrentModificationException,这被称为及时失效(fail-fast)。
- List<Student> students=Collections.synchronizedList(new ArrayList<Student>());
- //可能抛出ConcurrentModificationException
- for(Student student:students)
- doSomething(student);

为了防止抛出ConcurrentModificationException,需要在迭代之前对容器进行加锁,但是如果doSomething()比较耗时,那么其他线程都在等待锁,会极大降低吞吐率和CPU的利用率。不加锁的解决办法是“克隆”容器,在副本上进行迭代,由于副本被封闭在线程内,其他线程不会在迭代期间对其进行修改。克隆的过程也需要对容器加锁,开发人员要做出权衡,因为克隆容器本身也有显著的性能开销。
隐藏的迭代器
注意,下面的情况会间接地进行迭代操作,也会抛出ConcurrentModificationException:
- 容器的toString()、hashCode()和equals()方法
- containsAll()、removeAll()、retainAll()等方法
- 调用以上方法的方法,比如StringBuildre.append(Object)会调用toString()
- 容器作为另一个容器的元素或者键
- 把容器作为参数的构造函数
并发容器
并发Map
Striping)。结果是任意的读线程可以并发地访问Map,读线程和写线程可以并发地访问Map,一定数量的写线程可以并发地访问Map。而且在单线程的环境下,ConcurrentHashMap比HashMap性能损失很小。
Copy-On-Write容器
- public final class ThreeStorage{
- private final Set<String> storages=new HashSet<String>();
- public ThreeStorage(){
- storages.add("One");
- storages.add("Two");
- storages.add("Three");
- }
- public boolean isStorage(String name){
- return storages.contains(name);
- }
- }

虽然Set对象是可变的,但从ThreeStorage的设计上来看,Set对象在构造完成后无法对其进行修改。
- public class CopyOnWriteArrayList<E>{
- private volatile transient Object[] array;
- final Object[] getArray() {
- return array;
- }
- final void setArray(Object[] a) {
- array = a;
- }
- public E set(int index, E element) {
- //获取重入锁
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- Object[] elements = getArray();
- Object oldValue = elements[index];
- //使用的是==而非equals
- if (oldValue != element) {
- int len = elements.length;
- //复制底层数组
- Object[] newElements = Arrays.copyOf(elements, len);
- newElements[index] = element;
- //把底层数组写回
- setArray(newElements);
- } else {
- setArray(elements);
- }
- return (E)oldValue;
- } finally {
- //释放锁
- lock.unlock();
- }
- }
- }

并发Queue
Sorted容器
【转载】Java容器的线程安全的更多相关文章
- [转载]java自带线程池和队列详细讲解
FROM:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中 ...
- java容器的线程安全性
参考:https://www.cnblogs.com/yjd_hycf_space/p/7760248.html 线程安全的: Vector HashTable StringBuffer 线程不安全的 ...
- [转载]Java线程的两种实现方式
转载:http://baijiahao.baidu.com/s?id=1602265641578157555&wfr=spider&for=pc 前言 线程是程序的一条执行线索,执行路 ...
- java容器中 哪些是线程安全的
容器中线程安全的如:vectory,hashtable,非线程安全的如:hashmap,arrylist等. 对于原定义非线程的容器如:hashmap,arraylist可以使用Collec ...
- (转载)new Thread的弊端及Java四种线程池的使用
介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端 执行一个异步任务你还只是如下new ...
- [ 转载 ] Java基础14--创建线程的两个方法
http://www.cnblogs.com/whgw/archive/2011/10/03/2198506.html Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类 ...
- Java 容器(list, set, map)
java容器类库的简化图: (虚线框表示接口, 实线框表示普通的类, 空心箭头表示特定的类实现了接口, 实心箭头表示某个类可以生成箭头所指的类对象) 继承Collection的主要有Set 和 Lis ...
- Java - 容器详解
一.ArrayList 长度可变数组,类似于c++ STL中的vector. 元素以线性方式连续存储,内部允许存放重复元素. 允许对元素进行随机的快速访问,但是向ArrayList中插入和删除元素的速 ...
- [转载] Java集合框架之小结
转载自http://jiangzhengjun.iteye.com/blog/553191 1.Java容器类库的简化图,下面是集合类库更加完备的图.包括抽象类和遗留构件(不包括Queue的实现): ...
随机推荐
- java中给集合快速取值最大值和最小值
public static void main(String[] args) { List list = new ArrayList(); list.add(new Double(123.23)); ...
- AC自动机(模板+例题)
首先要明白AC自动机是干什么的: AC自动机其实就是一种多模匹配算法,那么你可能会问什么叫做多模匹配算法.下面是我对多模匹配的理解,与多模与之对于的是单模,单模就是给你一个单词,然后给你一个字符串,问 ...
- Dubbo的SPI机制与JDK机制的不同及原理分析
从今天开始,将会逐步介绍关于DUbbo的有关知识.首先先简单介绍一下DUbbo的整体概述. 概述 Dubbo是SOA(面向服务架构)服务治理方案的核心框架.用于分布式调用,其重点在于分布式的治理. 简 ...
- springboot里面的缓存注解
https://blog.csdn.net/u012240455/article/details/80844361 https://lfvepclr.gitbooks.io/spring-framew ...
- The Softmax function and its derivative
https://eli.thegreenplace.net/2016/the-softmax-function-and-its-derivative/ Eli Bendersky's website ...
- layer.open获取弹出层的input框的值
使用top.$('#txtReason').val();获取值: //不通过 function unAuditData(id) { parent.layer.open({ type: , title: ...
- ASP.NET Razor 常用示例
1.在网页中显示@符号 使用@@即可使编译器不切换到c#,这样在网页中会显示一个@符号. 2.隐式表达式 也就是正常的razor语法,不能包含空格.(除了await 如:<p>@await ...
- Docker镜像加速-配置阿里云镜像仓库
Docker默认远程仓库是https://hub.docker.com/ 比如我们下载一个大点的东西,龟速 由于是国外主机,类似Maven仓库,慢得一腿,经常延迟,破损: 所以我们一般都是配置国内镜像 ...
- 给阿里云主机添加swap分区,解决问题:c++: internal compiler error: Killed (program cc1plus)
前言 今天安装spdlog,一个快速得C++日志库,按照文档步骤,不料出现了一堆错误,像c++: internal compiler error: Killed (program cc1plus)等一 ...
- 09 部署nginx web服务器(转发uwsgi请求)
1 配置nginx转发 $ whereis nginx $ cd /usr/local/nginx/conf $ vi nginx.conf 注释掉原来的html请求,增加uwsgi请求. locat ...