【问题标题】:final List<T> and thread safetyfinal List<T> 和线程安全
【发布时间】:2021-11-14 03:36:11
【问题描述】:

我试图了解final 字段在多线程环境中的作用。

我阅读了这些相关的帖子:

final fields and thread-safety

Java concurrency: is final field (initialized in constructor) thread-safe?

Java: Final field freeze on object reachable from final fields

并尝试模拟final 字段将阻止线程使用未完全构造的对象的情况。

public class Main {
    public static void main(String[] args) {
        new _Thread().start();
        new Dummy();
    }

    static class _Thread extends Thread {
        static Dummy dummy;

        @Override
        public void run() {
            System.out.println(dummy.getIntegers().size() == 10_000);
        }
    }

    static class Dummy {
        private final List<Integer> integers = new ArrayList<>();

        public Dummy() {
            _Thread.dummy = this;

            for (int a = 0; a < 10_000; a++) {
                integers.add(a);
            }
        }

        public List<Integer> getIntegers() {
            return integers;
        }
    }
}

据我了解,_Thread 将在getIntegers() 上停止执行并等待循环完成填充收集。但是integers字段是否有final修饰符,run()的结果是不可预测的。我也知道有NPE的可能。

【问题讨论】:

  • 你到底在问什么?据我所知,这个“问题”实际上并不包含任何问题。只是一个意图声明,以及一些关于您认为该代码应该如何表现的断言。您实际上希望我们在这里回答什么?
  • IMO,您混淆了“完全构造”的含义。 ArrayList 实例在“完全构造”时为。然后,Dummy 构造函数改变列表,列表被“构造”之后。您对final 的使用可防止其他线程看到处于未构造状态的list,但您允许其他线程在Dummy 完全构造之前看到Dummy 实例。你允许它看到Dummy,而它的构造函数仍在改变列表。

标签: java multithreading final


【解决方案1】:

final 在这里没有区别。无论final 是否存在,代码都不是线程安全的。

这不是线程安全的有两个原因。

  1. 您在 Dummy 的构造函数完成之前发布(并可能改变)其状态。无论变量是否为final,这都是不安全的。

  2. 您在 getIntegers() 调用中返回了一个共享的可变对象。这意味着调用者可以更改它,第二个调用者可能会也可能不会看到结果......由于缺乏同步。再次final 对此没有任何影响。


final 的线程安全保证是有限的。这是JLS 所说的:

final 字段还允许程序员在不同步的情况下实现线程安全的不可变对象。线程安全的不可变对象被所有线程视为不可变的,即使使用数据竞争在线程之间传递对不可变对象的引用也是如此。这可以提供安全保证,防止不正确或恶意代码滥用不可变类。 final 字段必须正确使用以保证不变性。

当一个对象的构造函数完成时,它被认为是完全初始化的。只能在对象完全初始化后才能看到对该对象的引用的线程可以保证看到该对象的 final 字段的正确初始化值。

要点是final 保证仅适用于不可变对象,并且它们仅适用于对象的构造函数返回之后。

在您的示例中不满足这些先决条件中的任何一个。因此,保证不适用。

【讨论】:

  • 例如 int 这个问题 - stackoverflow.com/questions/6457109/… 使用了 final HashMap,所有值都放在构造函数中。我想知道如果没有 final 修饰符会有什么区别。至于我,我认为 bcs 没有区别,反正线程不会返回对象,直到构造函数完成他的工作,他没有从构造函数中传递出this。或者 bcs 不同步 Map 线程可以缓存他放入 Map 的值,而其他线程不会看到完全填充的地图,而只能看到其中的一部分?
  • final fields also allow programmers to implement thread-safe immutable objects without synchronization 如果只有单线程可以在其中执行代码,为什么我什至必须在构造函数内部使用同步?
  • 这是否意味着如果我将final 用于非同步收集,则在构造过程中放入的值不会缓存在线程内部,而是会在没有同步和收集的情况下提交到主内存中不是线程安全的,如果没有 final 修饰符,那么输入的值可能只对在构造函数中执行代码的线程可见?
  • 1) 是的。如果您满足所有的先决条件。 2) 是的。
  • 我稍微修改了代码new _Thread(new Dummy()).start(),但没有达到预期的效果。每次打印该列表时都会包含所有 10_000 个元素。这是因为start() 方法触发happens-before 并且主线程中构造函数的所有值都将在_Thread 中可见还是很难重现?
猜你喜欢
  • 1970-01-01
  • 2021-11-26
  • 2011-08-17
  • 1970-01-01
  • 1970-01-01
  • 2014-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多