当前位置: 首页 > 知识库问答 >
问题:

Java并发实践-示例14.12

漆雕唯
2023-03-14
// Not really how java.util.concurrent.Semaphore is implemented
@ThreadSafe
public class SemaphoreOnLock {
    private final Lock lock = new ReentrantLock();
    // CONDITION PREDICATE: permitsAvailable (permits > 0)
    private final Condition permitsAvailable = lock.newCondition();
    @GuardedBy("lock") private int permits;

    SemaphoreOnLock(int initialPermits) {
        lock.lock();
        try {
            permits = initialPermits;
        } finally {
            lock.unlock();
        }
    }

/* other code omitted.... */

我对上面的示例有一个问题,该示例是从Java并发实践清单14.12使用锁实现的计数信号量中提取的。

我想知道为什么我们需要获取构造函数中的锁(如图所示lock.lock()被调用)。据我所知,构造函数是原子的(除了引用转义),因为没有其他线程可以获得引用,因此,半构造对象对其他线程不可见。因此,我们不需要构造函数的同步修饰符。此外,我们也不需要担心内存可见性,只要对象安全发布。

那么,为什么我们需要在构造函数中获取ReentrantLock对象呢?

共有3个答案

柳豪
2023-03-14

(只是为了我自己可怜的头脑澄清一下——其他答案是正确的)。

这个假设的SemaphoreOnLock类的实例旨在被共享。因此线程T1完全构造了一个实例,并将其放在线程T2可以看到它的地方并调用一些需要读取许可字段的方法。关于许可字段需要注意的一些重要事项:

  1. 在第一种情况下,它被初始化为默认值0

因此,如果我们想读取上次写入的值,我们需要同步。我们必须在构造函数中这样做,就像在其他任何情况下一样。(它是否为原子赋值的事实并不影响这个可见性问题)。将构造的信号量锁(SemaphoreOnLock)限制在单个线程中的策略对我们不起作用,因为使其成为线程安全的整个想法是为了我们可以安全地共享它。

本例说明的是,当将任何非静态、非最终、非易失性字段设置为其默认值以外的值时,“线程安全”也适用于对象的构造。

当然,当我们有一个@NotThreadsecurity类时,我们甚至没有义务考虑这个问题。如果调用者构造了我们,并决定在两个线程之间共享我们,那么调用者必须安排适当的同步。在这种情况下,我们可以在构造函数中做任何我们喜欢的事情,而不用担心可见性问题——那是别人的问题。

梁福
2023-03-14

老实说,我在这里看不到锁的任何有效用途,除了它引入了内存Geofence这一事实。无论如何,int分配在32/64位上是原子的。

邵鸿福
2023-03-14

半构造的对象对其他线程不可见

这不是真的。如果对象具有任何非最终/易失性字段,则在构建时其他线程可以看到该对象。因此,其他线程可能会看到“允许”的默认值,即“0”,这可能与当前线程不一致。

Java内存模型为不可变对象(只有最终字段的对象)提供了初始化安全的特殊保证。对另一个线程可见的对象引用并不一定意味着该对象的状态对消费线程可见-JCP$3.5.2

从Java并发实践的清单3.15中可以看出:

虽然在构造函数中设置的字段值似乎是写入这些字段的第一个值,因此没有“旧”值可以视为陈旧值,但Object构造函数在子类构造函数运行之前首先将默认值写入所有字段。因此,可以将字段的默认值视为陈旧值。

 类似资料:
  • 问题内容: 我对上面的示例有一个疑问,该示例是从 练习 清单14.12计数使用锁实现的信号量的 Java并发中 提取的。 我想知道为什么我们需要在构造函数中获取锁(如所示的lock.lock()被调用)。据我所知,构造函数是 原子的 (引用转义除外),因为没有其他线程可以获取该引用,因此,其他线程看不到一半构造的对象。因此,我们不需要构造函数的同步修饰符。此外,只要对象被安全发布,我们也不必担心

  • 问题内容: 我有以下来自GoByExamples的并发渠道示例 Java中是否有等同的东西?我本以为实现同一件事会更加冗长。 问题答案: 语句 是 在Go中在语言语法级别引入并发的原因。并发函数调用可以(和通常完成)在库级别使用辅助函数(例如和通道)实现,就像在大多数其他语言中使用互斥或​​锁定的数据结构一样。但是声明不能。

  • 问题内容: 在《 Java Concurrency In Practice》中,作者指出 不变对象可以通过任何机制发布 不可变对象可以在没有附加同步的情况下被任何线程安全地使用,即使不使用同步来发布它们。 这是否意味着以下成语可以发布不可变对象? 会有数据竞赛吗?(这意味着线程B可能无法在线程A添加的列表中看到不可变对象) 非常感谢你。 此外,作者说,如果Resource是不可变的,以下代码是安全

  • null 对于此问题的第二部分,将在另一个问题(单击此处)中详细讨论

  • 问题内容: 是否仍然有效?我想知道本书中描述的思想,概念和实现是否仍与最新的Java版本兼容。 我问是因为最新版本是2006年完成的。 问题答案: 是的,在我看来仍然有效。从6到7的变化不大。Fork- join是一项新功能,非常适合分治法式的问题。但是本书中所有现有的东西,例如同步,易失性,servlet,仍然非常有效。

  • 问题内容: Brian Goetz的Java Concurrency in Practice提供了一个有效的可伸缩缓存供并发使用的示例。这是该类的代码: 可能是个愚蠢的问题,但有人能告诉我该类的并发用法吗?喜欢在主? 干杯,Agata 问题答案: 这是一个计算阶乘的示例: 因此,如果两个线程尝试在完全相同的时间计算相同的阶乘,则由于线程安全,因此只有其中一个将实际执行计算。第二个线程将简单地获取第