Java 了解集合并发性和集合.synchronized*
昨天我了解到,多年来我一直错误地使用并发集合 每当我创建一个需要由多个线程访问的集合时,我都会将其包装在一个Collections.synchronized*方法中。然后,每当对集合进行变异时,我也会将它包装在一个同步块中(我不知道为什么要这样做,我一定以为我在什么地方读过它) 但是,在仔细阅读API之后,在迭代集合时似乎需要同步块。从API文档(用于地图)中: 当用户迭代其任何集合视图时,必须手动同步返回的映射: 下面是一个小例子:Java 了解集合并发性和集合.synchronized*,java,collections,concurrency,Java,Collections,Concurrency,昨天我了解到,多年来我一直错误地使用并发集合 每当我创建一个需要由多个线程访问的集合时,我都会将其包装在一个Collections.synchronized*方法中。然后,每当对集合进行变异时,我也会将它包装在一个同步块中(我不知道为什么要这样做,我一定以为我在什么地方读过它) 但是,在仔细阅读API之后,在迭代集合时似乎需要同步块。从API文档(用于地图)中: 当用户迭代其任何集合视图时,必须手动同步返回的映射: 下面是一个小例子: List<O> list = Collectio
List<O> list = Collections.synchronizedList(new ArrayList<O>());
...
synchronized(list) {
for(O o: list) { ... }
}
List List=Collections.synchronizedList(新的ArrayList());
...
已同步(列表){
对于(O O:list){…}
}
因此,鉴于此,我有两个问题:
- 为什么这是必要的?我能想到的唯一解释是他们使用默认迭代器而不是托管线程安全迭代器,但他们本可以创建一个线程安全迭代器并修复这个混乱,对吗
- 更重要的是,这实现了什么?通过将迭代放在同步块中,可以防止多个线程同时迭代。但是另一个线程可以在迭代时改变列表,那么同步块在这方面有什么帮助呢?在其他地方对列表进行变异是否会影响迭代,无论迭代是否同步?我错过了什么
Collections.synchronizedList()
返回的对象的所有方法都与列表对象本身同步。每当从一个线程调用一个方法时,调用该方法的任何其他线程都会被阻止,直到第一次调用完成
到目前为止还不错
这是非常必要的
但这并不能阻止另一个线程在其迭代器上调用到next()
时修改集合。如果发生这种情况,您的代码将失败,出现ConcurrentModificationException
。但是,如果您也在synchronized
块中进行迭代,并且您在同一对象(即列表)上进行同步,这将阻止其他线程调用列表上的任何mutator方法,它们必须等待您的迭代线程释放列表对象的监视器关键是mutator方法与迭代器块同步到同一个对象,这就是阻止它们的原因。
我们还没有脱离险境。。。
请注意,尽管上述方法保证了基本的完整性,但并不能保证行为始终正确。在多线程环境中,您可能会有其他部分的代码进行假设:
List<Object> list = Collections.synchronizedList( ... );
...
if (!list.contains( "foo" )) {
// there's nothing stopping another thread from adding "foo" here itself, resulting in two copies existing in the list
list.add( "foo" );
}
...
synchronized( list ) { //this block guarantees that "foo" will only be added once
if (!list.contains( "foo" )) {
list.add( "foo" );
}
}
List List=Collections.synchronizedList(…);
...
如果(!list.contains(“foo”)){
//没有什么可以阻止另一个线程在这里添加“foo”,从而导致列表中存在两个副本
列表。添加(“foo”);
}
...
synchronized(list){//此块保证只添加一次“foo”
如果(!list.contains(“foo”)){
列表。添加(“foo”);
}
}
线程安全迭代器?
至于关于线程安全迭代器的问题,它确实有一个列表实现,叫做。它非常有用,但正如API文档中所指出的,它仅限于少数用例,特别是当您的列表很少被修改,但迭代次数如此频繁(并且由如此多的线程)以至于同步迭代将导致严重的瓶颈时。如果使用不当,会大大降低应用程序的性能,因为列表的每一次修改都会创建一个完整的新副本
为什么这是必要的?我能想到的唯一解释是
他们使用的是默认迭代器,而不是托管线程安全的迭代器
迭代器,但他们可以创建线程安全迭代器并修复
这一团糟,对吧
迭代一次只能使用一个元素。为了使迭代器是线程安全的,他们需要复制集合。如果做不到这一点,对基础集合的任何更改都会影响您以不可预测或未定义的结果进行迭代的方式
更重要的是,这实现了什么?通过将迭代
在同步块中,您正在阻止多个线程
同时迭代。但另一个线程可能会改变列表
在迭代过程中,同步块在这方面有什么帮助?
在其他地方对列表进行变异不会影响迭代吗
是否同步?我错过了什么
synchronizedList(List)
返回的对象的方法通过同步实例来工作。因此,当您在列表
上的已同步
块中时,没有其他线程可以添加/删除同一列表
,必须在返回的列表上同步,因为内部操作在互斥体
上同步,而互斥体就是此
,即同步收集本身
下面是同步集合层次结构的根SynchronizedCollection
的一些构造函数
SynchronizedCollection(Collection<E> c) {
if (c==null)
throw new NullPointerException();
this.c = c;
mutex = this;
}
SynchronizedCollection(集合c){
如果(c==null)
抛出新的NullPointerException();
这个.c=c;
互斥=这个;
}
(还有另一个构造函数接受互斥锁,用于初始化来自子列表
等方法的同步“视图”集合)
如果您在同步列表本身上进行同步,那么当您在列表上进行迭代时,确实可以防止另一个线程对列表进行变异
同步的命令