java并发-同步容器类
java平台类库包含了丰富的并发基础构建模块,如线程安全的容器类以及各种用于协调多个相互协作的线程控制流的同步工具类。
同步容器类
同步容器类包括Vector和Hashtable,是早期JDK的一部分,此外还有Collections.synchronizedXXX等工厂方法创建的。这些类实现安全的方式是,将他们的状态封装起来,并对每个public方法进行同步,从而 使得每次只有一个线程能访问容器的状态。
同步容器类都是线程安全的,但是对于某些复合操作需要额外的加锁来保护。常见复合操作有:迭代(反复访问元素,直到遍历所有元素)、跳转(根据指定顺序找到当期元素的下一个元素)以及条件运算(如:如没有则添加)。
public static Object getLast(Vector list){
int lastIndex = list.size() - 1;
return list.get(lastIndex)
}
public static void deleteLast(Vector list){
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
上面例子中,Vector中定义了两个方法,它们都执行先检查再运行操作。先获取数组大小,再获取或删除最后一个元素。这些方法看似没问题,并且都是线程安全的,也不破坏Vector。但是从调用者角度来看,就有问题了。可能A线程调用getLast的过程中,B线程调用了deleteLast,Vector元素减少,导致A线程调用失败。
同步容器类遵守同步策略,即支持客户端加锁,因此只要我们知道应该使用那个锁,就能创建一些新的操作。这些新操作与容器与其他操作都是原子操作。同步容器通过自身的锁来保护它的每个方法。通过获取容器的锁,就能使上面的方法称为原子操作。size和get操作之间不会有其他操作。
public static Object getLast(Vector list){
synchronized(list){
int lastIndex = list.size() - 1;
return list.get(lastIndex)
}
}
public static void deleteLast(Vector list){
synchronized(list){
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
}
同样的问题也会出现在遍历上,如下面的例子:
for(int i=0; i< vector.size(); i++)
doSomgthing(vector.get(i));
如果另外一个线程删除一个元素,会导致ArrayIndexOutBoundsException异常。我们可以通过加锁来解决迭代不可靠问题,避免其他线程在遍历过程修改Vector。但也带来性能问题,迭代期间其他线程无法访问它。
synchronized(vector){
for(int i=0; i< vector.size(); i++)
doSomgthing(vector.get(i));
}
上面的例子在未加锁的情况下都可能抛出异常,这并不意味着Vector不是线程安全的。Vector仍然是线程安全的,抛出的异常也与其规范保持一致。
迭代器与ConcurrentModificationException
对容器进行迭代的标准方式是使用Iterator,使用for-each语法,也是调用Iterator。在设计同步容器类的时候并没有考虑并发修改问题,它们表现出的行为是“及时失败”的,意味着在迭代过程中,如果有其他线程修改容器,会抛出ConcurrentModificationException异常。它们实现的方式是,将计数器变化与容器关联起来,放迭代器件计数器被修改,那么hasNext或next将抛出异常。这是设计上的一个权衡。
要想避免ConcurrentModificationException,就必须在迭代过程持有容器的锁。但是如果容器规模很大,迭代过程持有锁,将导致严重的性能问题。一种替代方式就是“克隆容器”,并在副本上迭代。克隆过程仍然需要加锁,同时存在显著的性能开销。克隆容器的好坏取决于过个元素,如容器大小,迭代时,每个元素执行的操作等。
隐藏迭代器
加锁可以防止迭代抛出ConcurrentModificationException异常,但是需要在所有迭代的地方进行加锁。实际情况通常更加复杂,有些情况下可能会忽略隐藏的迭代器。
public class HiddenIterator{
private final Set(Integer) set = new HashSet<>();
public synchronized void add(Integer i){set.add(i)}
public synchronized void remove(Integer i){set.remove(i)}
public void addTenThings(){
Random r = new Random();
for(int i=0;i<10;i++){
add(r.nextInt());
}
System.out.println("debug" + set)
}
}
addTenThings方法可能抛出ConcurrentModificationException异常,因为在打印输出的时候进行字符串连接,会调用set的toString方法,toString方法会对容器进行迭代。在使用println前必须获取HiddenIterator的锁,但是实际应用中可能忽略。
封装对象的状态有助于维持不变性,封装对象的同步机制有助有确保实施同步策略。
如果使用synchronizedSet来包装HashSet,并且对同步代码进行封装,就不会发生这种错误。
除了toString,hashCode和equals等方法也会间接执行迭代操作。当容器作为另一个容器的元素和键值时,就会出现这种情况。同样,containsAll,removeAll等方法,以及把容器作为参数的构造函数都会对容器进行迭代。这些间接操作都有可能抛出ConcurrentModificationException异常。
java并发-同步容器类的更多相关文章
- Java并发-同步容器篇
作者:汤圆 个人博客:javalover.cc 前言 官人们好啊,我是汤圆,今天给大家带来的是<Java并发-同步容器篇>,希望有所帮助,谢谢 文章如果有问题,欢迎大家批评指正,在此谢过啦 ...
- Java并发——同步容器与并发容器
同步容器类 早期版本的JDK提供的同步容器类为Vector和Hashtable,JDK1.2 提供了Collections.synchronizedXxx等工程方法,将普通的容器继续包装.对每个共有方 ...
- Java并发--同步容器
为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...
- Java并发—同步容器和并发容器
简述同步容器与并发容器 在Java并发编程中,经常听到同步容器.并发容器之说,那什么是同步容器与并发容器呢?同步容器可以简单地理解为通过synchronized来实现同步的容器,比如Vector.Ha ...
- Java多线程——同步容器类
1.同步容器类 同步容器类包括Vector和Hashtable,是早期JDK的一部分,这些类实现的方法是:将它们的状态封装起来,并对每个共有的方法进行同步,使得每个线程只有一个线程能访问它们. 1.1 ...
- Java并发——同步工具类
CountDownLatch 同步倒数计数器 CountDownLatch是一个同步倒数计数器.CountDownLatch允许一个或多个线程等待其他线程完成操作. CountDownLatch对象 ...
- JAVA并发同步互斥实现方式总结
大家都知道加锁是用来在并发情况防止同一个资源被多方抢占的有效手段,加锁其实就是同步互斥(或称独占)也行,即:同一时间不论有多少并发请求,只有一个能处理,其余要么排队等待,要么放弃执行.关于锁的实现网上 ...
- Java 并发同步工具(转)
转自:https://www.jianshu.com/p/e80043ac4115 在 java 1.5 中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如 CountDownLatch,Cy ...
- Java 并发同步器之CountDownLatch、CyclicBarrier
一.简介 1.CountDownLatch是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞 ...
随机推荐
- BZOJ3591 最长上升子序列(状压dp)
之前听说过一种dp套dp的trick,大致是用另一个dp过程中用到的一些东西作为该dp的状态.这个题比较类似. 考虑求LIS时用到的单调队列.设f[S]为所选取集合为S的方案数,其中在单调队列内的标2 ...
- 【数组】- ArrayList自动扩容机制
不同的JDK版本的扩容机制可能有差异 实验环境:JDK1.8 扩容机制: 当向ArrayList中添加元素的时候,ArrayList如果要满足新元素的存储超过ArrayList存储新元素前的存储能力, ...
- linux设置开机自动启动
有很多中方法,这里只取最简单的一种: 把启动命令放到/etc/rc.d/rc.local文件里这样就可以每次启动的时候自动启动服务了, 注意给rc.local执行权限
- 特征点检测学习_2(surf算法)
依旧转载自作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 特征点检测学习_2(surf算法) 在上篇博客特征点检测学习_1(sift算法) 中 ...
- ICPC 2018 南京网络赛 J Magical Girl Haze(多层图最短路)
传送门:https://nanti.jisuanke.com/t/A1958 题意:n个点m条边的路,你有k次机会将某条路上的边权变为0,问你最短路径长度 题解:最短路变形,我们需要在常规的最短路上多 ...
- Qt ------ Thread Affinity (QObject::moveToThread: Cannot move objects with a parent)
简单的说,每个QObject的对象,都和某个创建对象所在的线程关联,如果把对象通过 moveToThread 移动到其他线程,这个对象不能有父对象,否则会出现 QObject::moveToThrea ...
- \G,sql中select 如果太长,可以在后面放\G,竖行显示~~~~
1.使用\G按行垂直显示结果 如果一行很长,需要这行显示的话,看起结果来就非常的难受. 在SQL语句或者命令后使用\G而不是分号结尾,可以将每一行的值垂直输出. mysql> select * ...
- python--文件操作删除某行
使用continue跳过本次写循环就可以了 #文本内容 Yesterday when I was young 昨日当我年少轻狂 The tasting of life was sweet 生命的滋味是 ...
- 「Python」人脸识别应用
人脸识别主要步骤: face_recognition 库的安装 安装此库,首先需要安装编译dlib,此处我们偷个懒,安装软件Anaconda(大牛绕过),此软件预装了dlib. 安装好后,我们直接通过 ...
- 前端PHP入门-029-文件操作-掌握级别
人生只有三天,活在昨天的人迷惑:活在明天的人等待:活在今天的人最踏实.你永远无法预测意外和明天哪个来得更早,所以,我们能做的,就是尽最大的努力过好今天.请记住:今天永远是昨天死去的人所期待的明天. f ...