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

对Java中的安全发布和可见性感到困惑,尤其是对不可变对象

宗政天逸
2023-03-14

现在,我在https://www.cs.umd.edu/~pugh/java/memorymodel/jsr-133-faq.html中读到,“现在,在说了所有这些之后,如果在一个线程构造了一个不可变的对象(即一个只包含final字段的对象)之后,您希望确保其他所有线程都能正确地看到它,那么您通常仍然需要使用同步。没有其他方法可以确保,例如,对不可变对象的引用会被第二个线程看到。程序从最终字段得到的保证应该通过对代码中如何管理并发的深入和仔细的理解来仔细调整。“

他们似乎互相矛盾,我不知道该相信哪一个。

我还读到,如果所有字段都是最终的,那么即使对象不是不可变的,我们也可以确保安全发布。例如,我一直认为Brian Goetz在实践中的并发中的这段代码由于这种保证在发布该类的一个对象时是很好的。

@ThreadSafe
public class MonitorVehicleTracker {
    @GuardedBy("this")
    private final Map<String, MutablePoint> locations;

    public MonitorVehicleTracker(
            Map<String, MutablePoint> locations) {
        this.locations = deepCopy(locations);
    }

    public synchronized Map<String, MutablePoint> getLocations() {
        return deepCopy(locations);
    }

    public synchronized MutablePoint getLocation(String id) {
        MutablePoint loc = locations.get(id);
        return loc == null ? null : new MutablePoint(loc);
    }

    public synchronized void setLocation(String id, int x, int y) {
        MutablePoint loc = locations.get(id);
        if (loc == null)
            throw new IllegalArgumentException("No such ID: " + id);
        loc.x = x;
        loc.y = y;
    }

    private static Map<String, MutablePoint> deepCopy(
            Map<String, MutablePoint> m) {
        Map<String, MutablePoint> result =
            new HashMap<String, MutablePoint>();
        for (String id : m.keySet())
            result.put(id, new MutablePoint(m.get(id)));
        return Collections.unmodifiableMap(result);
    }
}
public class MutablePoint { /* Listing 4.5 */ }

共有1个答案

郝池暝
2023-03-14

这个问题以前曾回答过几次,但我觉得很多答案都是不足够的。参见:

  • https://stackoverflow.com/A/14617582
  • https://stackoverflow.com/A/35169705
  • https://stackoverflow.com/a/7887675
  • 有效不可变对象
  • 等..

简而言之,Goetz在链接的JSR133 FAQ页面中的声明更加“正确”,尽管不是按照您所想的方式。

说明第一条JCIP语句的一个好方法是删除volatile。在这种情况下,cachedfactorizer将无法工作。考虑一下:如果一个线程设置了一个新的缓存值,但另一个线程试图读取该值,而该字段不是volatile呢?读者可能看不到更新的onevalueCache。但是,回想一下Goetz引用的是不可变对象的状态,如果读取器线程碰巧看到存储在缓存中的OnEvalueCache的最新实例,那么该实例的状态将是可见的并且正确构造的。

因此,虽然有可能丢失对缓存的更新,但如果读取OnEvalueCache则不可能丢失其状态,因为它是不可变的。我建议阅读附带的文字,说明“用于确保及时可见性的易失性引用。”

作为最后一个示例,考虑一个使用finalwrapper来实现线程安全的单例。注意,FinalWrapper实际上是不可变的(取决于单例是否可变),helperwrapper字段实际上是非易失的。回想第二个FAQ语句,访问引用需要同步,这个“正确的”实现怎么可能是正确的!?

如果是第一个调用,则在同步上下文中再次检查字段本身,如第二个FAQ语句所建议的。它将发现此值仍然为空,并将初始化一个新的finalwrapper并同步发布。

如果它只是一个过时的值,那么通过输入synchronized块,线程可以设置一个happens-before顺序,并对字段执行前面的写入。根据定义,如果一个值是过时的,那么某个编写器已经写入helperwrapper字段,而当前线程还没有看到它。通过进入synchronized块,将与前面的写建立一个happens-before关系,因为根据我们的第一个场景,一个真正未初始化的helperwrapper将由同一个锁初始化。因此,一旦方法进入同步上下文,它就可以通过重新读取来恢复,并获得最新的非空值。

我希望我的解释和所附的例子能为你们澄清。

 类似资料:
  • 问题内容: 在碰到此链接http://www.javacodegeeks.com/2013/01/java-thread-pool-example-using- executors-and-threadpoolexecutor 之后,这是我第一次为新项目使用Java线程池。 .html ,我对此更加困惑,这是页面中的代码, 在代码中,创建了一个固定大小的池并创建了10个工作线程,对吗? 线程池应该

  • 及其不安全的发布: 可以抛出AssertionError,我同意。作者写道,这是因为不安全的出版,但另一方面没有答案:什么才是正确的出版方式?它们表示了4个安全发布习惯用语,但我不明白,为什么它们会在上面的情况下起作用: 要安全地发布对象,必须同时使对对象的引用和对象的状态对其他线程可见。通过以下方法可以安全地发布构造正确的对象: null 这里是我的第一个问题,谢谢你的帮助!

  • 我正在尝试提出一种解决方案,它涉及在连接操作之后应用一些逻辑,从多个中的中选择一个事件。这类似于reduce函数,但它只返回1个元素,而不是递增地返回。因此最终结果将是单个(,对,而不是一个 每个键保证只到达一次。 假设像上面这样的连接操作,它用4个生成了1个,成功地连接并收集在。现在,我想做的是,立即访问这些值,并执行一些逻辑以将正确匹配到一个。例如,对于上面的数据集,我需要(,和)。 将为每个

  • 所以我一直在读Kafka的语义学,我对它的工作原理有点困惑。 我理解生产者如何避免发送重复的消息(以防代理的ack失败),但我不明白的是,在消费者处理消息但在提交偏移量之前崩溃的情况下,一次是如何工作的。Kafka不会在这种情况下重试吗?

  • 问题内容: 我已经在eclipse中创建了一个项目,并添加了Maven依赖项。在Eclipse中,它表示我正在使用JRE 1.5。一切在Eclipse中都可以正常运行,例如,我可以运行测试。 当我尝试从终端运行时,出现以下错误。 …在-source 1.3中不支持泛型(使用-source 5或更高版本来启用泛型)… 看来,Maven认为我正在使用JRE 1.3,并且无法识别泛型或for-each循

  • 问题内容: 我可以理解以下定义: 每个对象都有一个标识,一个类型和一个值。一旦创建了对象,其身份就永远不会改变。您可能会认为它是对象在内存中的地址。所述操作者比较两个对象的身份; 该函数返回一个表示其身份的整数。 我认为上面的定义在创建“某物”时起作用,例如: 但是我不理解: 我还没有创建任何东西。那么整数“ 1”如何具有ID?这是否意味着只要我在Python Shell中“提及” 1,便立即将其