并发场景中安全发布对象

698 阅读4分钟

安全发布

不正确的发布:正确的对象被破坏

不能指望一个尚未被完全创建的对象拥有完整性。某个观察该对象的线程将看到对象处于不一致的状态,然后看到对象的状态突然发生变化,即使线程在对象发布后还没有修改过它。

不可变对象与初始化安全性

任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有使用同步。

安全发布的常用模式

要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他对象线程可见。一个正确构造的对象可以通过以下方式来安全地发布:

  • 在静态初始化函数中初始化一个对象引用
  • 将对象的引用保存到volatile类型的域或者AtomicReferance对象中
  • 将对象的引用保存到某个正确构造对象的final类型域中
  • 将对象的引用保存到一个由锁保护的域中

线程安全库中的容器类提供了以下的安全发布保证:

  • 通过将一个键或者值放入HashtablesynchronizedMap、或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程(无论是直接访问还是通过迭代器访问)
  • 通过将某个元素放入VectorCopyOnWriteArrayListCopyOnWriteArraySetSynchronizedList或者SynchronizedSet中,可以将该元素安全地发布到任何从从这些容器中访问该元素的线程
  • 通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue,可以将该元素安全地发布到任何从这些队列中访问该元素的线程

通常,要发布一个静态构造对象,最简单和最安全的方式是使用静态的初始化器:

public static Holder holder = new Holder(42);

静态初始化器由JVM在类的初始化阶段执行。由于在JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布。

事实不可变对象

如果对象在发布后不会被修改,那么对于其他在没有额外同步的情况下安全地访问这些对象的线程来说,安全发布是足够的。所有的安全发布机制都能确保,当对象的引用对所有访问该对象的线程可见时,对象发布时的状态对于所有线程也将是可见的,并且如果对象状态不会再改变,那么就足以确保任何访问都是安全的。

如果对象从技术上来看是可变的,但其状态在发布后不会再改变,那么把这种对象成为“事实不可变对象(Effectively Immutable Object”。

没有额外的同步的情况下,任何线程都可以安全地使用被安全发布的事实不可变对象。

可变对象

如果对象在构造后可以修改,那么安全发布只能确保“发布当时”状态可见。对于可变对象,不仅在发布对象时需要使用同步,而且在每次对象访问时同样需要使用同步来确保后续修改操作的可见性。要安全地共享可变对象,这些对象就必须被安全地发布,并且必须是线程安全的或者由某个锁保护起来。

对象的发布需求取决于它的可变性:

  • 不可变对象可以通过任意机制发布
  • 事实不可变对象必须通过安全方式来发布
  • 可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。

安全地共享对象

在并发程序中使用和共享对象时,可以使用一些实用的策略:

  • 线程封闭:线程封闭的对象只能由一个线程拥有,对象封闭在该线程中,并且只能由这个线程修改。
  • 只读共享:在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。
  • 线程安全共享:线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口进行访问而不需要进一步的同步。
  • 保护对象:被保护的对象只能通过持有特定的锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。