使用任何 java.util.concurrent 类时,我是否仍需要同步对实例的访问以避免不同线程之间的可见性问题?
更详细地阐述这个问题
当使用 java.util.concurrent 的实例时,一个线程是否有可能修改该实例(即,将一个元素放入并发 hashmap 中)而后续线程不会看到修改?
我的问题源于这样一个事实,即如果对值的访问不同步,Java 内存模型允许线程缓存值而不是直接从内存中获取它们。
使用任何 java.util.concurrent 类时,我是否仍需要同步对实例的访问以避免不同线程之间的可见性问题?
当使用 java.util.concurrent 的实例时,一个线程是否有可能修改该实例(即,将一个元素放入并发 hashmap 中)而后续线程不会看到修改?
我的问题源于这样一个事实,即如果对值的访问不同步,Java 内存模型允许线程缓存值而不是直接从内存中获取它们。
在 java.util.concurrent 包Memory Consistency Properties上,您可以检查包的 Javadoc API:
java.util.concurrent 及其子包中所有类的方法将这些保证扩展到更高级别的同步。尤其是:
- 在将对象放入任何并发集合之前的线程中的操作 发生在另一个线程中从集合中访问或删除该元素之后的操作。
[...]- “释放”同步器方法(例如
Lock.unlock、Semaphore.release 和
CountDownLatch.countDown
)之前的操作发生在成功“获取”方法(例如 Lock.lock、Semaphore.acquire、Condition.await 和 CountDownLatch)之后的操作.await 在另一个线程中的同一个同步器对象上。
[...]
因此,这个包中的类确保了并发性,利用一组类进行线程控制(Lock
、Semaphore
等)。这些类以编程方式处理发生之前的逻辑,即管理并发线程的 FIFO 堆栈,锁定和释放当前和后续线程(即使用Thread.wait()
andThread.resume()
等。
然后,(理论上)您不需要同步访问此类的语句,因为它们以编程方式控制并发线程访问。
因为 ConcurrentHashMap(例如)被设计为在并发上下文中使用,所以您不需要进一步同步它。事实上,这样做可能会破坏它引入的优化。
例如, Collections.synchronizedMap(...) 代表了一种使地图线程安全的方法,据我所知,它本质上是通过将所有调用包装在 synchronized 关键字中来工作的。另一方面,像 ConcurrentHashMap 这样的东西会在集合中的元素之间创建同步的“桶”,从而导致更细粒度的并发控制,因此在大量使用下减少锁争用。例如,它也可能不会锁定读取。如果你用一些同步访问再次包装它,你可能会破坏它。显然,您必须注意所有对集合的访问都是同步的,这是新库的另一个优势;你不必担心(尽可能多!)。
java.lang.concurrent 集合可以通过同步实现它们的线程安全。在这种情况下,语言规范保证可见性。他们可以在不使用锁的情况下实现事物。我对此不是很清楚,但我认为这里会有相同的可见性。
如果您在代码中看到看起来像是丢失更新的内容,则可能只是竞争条件。ConcurrentHashpMap 之类的东西会在读取时为您提供最新的值,而写入可能尚未写入。这通常是准确性和性能之间的权衡。
重点是; java.util.concurrent 的东西是用来做这些东西的,所以我相信它可以确保可见性和使用 volatile 和/或添加同步化是不需要的。