为什么java并发集合真的是线程安全的

时间:2010-11-23 18:19:28

标签: java multithreading collections concurrency thread-safety

我正在查看java并发集合的代码,我看到它们只是在操作开始时锁定一些锁定并在最后将其解锁时包装简单集合。

volatile怎么样?如果后端集合不是易失性的,那么其他线程可能会错过更改,因此节省了一些线程。我知道synchronized可以解决这个问题,但是他们只使用锁而没有任何进一步的同步。

这是一个问题,还是我错过了什么?


更新

经过一番讨论后,我想稍微改一下这个问题。

我想在多线程环境中使用java集合。 (例如,目前我在谈论PriorityBlockingQueue

我想确保一个线程对集合(push / pop)所做的更改立即被其他人看到。

当并发线程数更新它时,java并发收集阻止我陷入麻烦以保持集合的内部状态稳定是好的 我想确保数据本身对所有线程都可见。

问题是:我是否认为java并发集合不提供开箱即用的功能?如果我这样做,我应该使用哪些额外的(简约成本)技术来提供所需的可见性?

感谢。

5 个答案:

答案 0 :(得分:11)

来自BlockingQueue的Javadoc:

  

内存一致性效果:与其他并发集合一样,在将对象放入BlockingQueue 之前,线程中的操作发生在之后的操作中,该操作是在访问或删除该元素之后执行的另一个帖子中的BlockingQueue

PriorityBlockingQueue通过ReentrantLockLock的实现)提供此行为:

  

...提供与内置监视器锁提供的内存同步语义相同的内存同步语义,如JLS ...

中所述

答案 1 :(得分:8)

是的,你错过了一些东西。 ReentrantLock类提供与synchronized相同的保证。并且,ReentrantLock和synchronized都提供与volatile相同的内存保证。

答案 2 :(得分:6)

  

我正在查看java的代码   并发集合,我明白了   他们只是用简单的集合包装   在开头锁定一些锁   操作和解锁它   端。

你在读什么来源?

这是一种过度概括。这完全取决于您正在查看的集合。例如,CopyOnWriteArrayList不执行此类操作,但每次添加或删除元素时都会创建一个全新的数组,这意味着任何打开的IteratorListIterator都将继续使用旧数据运行;这种影响是有意的。

  

挥发性怎么样?如果是后端   收集不易变化   可能会被其他线程错过,并且   所以线程节省有点   破碎。我知道同步可以   解决这个问题,但他们只使用   没有任何进一步的锁   同步。

大多数并发集合的存在是为了确保迭代器继续在旧版本上运行,而不是在已更新数据的新版本上运行; volatile无法保证的东西。此行为还意味着迭代器不会处于不一致状态,从而阻止抛出ConcurrentModificationException

答案 3 :(得分:5)

编辑:由于原来的问题得到澄清,这个答案已经不再适用了。下面的答案与使用Collections.synchronized*方法使非线程安全集合线程安全的情况相关。


如果同步一个代码块,它也会导致不同的线程同步(可能已更改)状态。

[...] 两种解决方案都要求clkID变量与主内存协调。从synchronized方法或块访问clkID变量不允许该代码同时执行,但它确保主内存中的clkID变量得到适当更新。在受保护代码执行之前获取对象锁定时更新主存储器,然后在受保护代码执行之后释放锁定时更新主存储器。 [...]

来源Use Synchronized or Volatile when Accessing Shared Variables

答案 4 :(得分:0)

以下是您的问题的明智答案。 1.并发包中的所有Java集合数据结构都不包装相应的非线程安全集合数据结构。 2.有一些方法可以在不使用锁或同步的情况下实现线程安全。编写无锁数据结构更为复杂,通常应避免进行一般开发。但是如果有一个可用,则应该使用它们而不是数据结构的相应锁定版本。 3.无锁数据结构不使用锁,仍提供所有安全性和活跃性。 4.示例API是ConcurrentSkipListMap,它是无锁的并发概率图(可能是Java中最复杂的实现)