在多线程环境中以下列方式构造对象是否安全?

时间:2019-03-11 15:19:48

标签: java concurrency constructor

public class DataEvent {
    private static final AtomicInteger lastRevision = new AtomicInteger();
    private final int revision;
    private final long threadId;
    private final long timestamp;

    private DataEvent(int revision) {
        this.revision = revision;
        this.threadId = Thread.currentThread().getId();
        this.timestamp = System.nanoTime();
    }

    public static DataEvent newInstance() {
        return new DataEvent(lastRevision.incrementAndGet());
    }
}

我的问题如下:

  • 说所有对象将一一一致地构造是绝对正确的吗?我的意思是,每个新对象的构建都比先前的对象晚。换句话说,每个新对象的timestamp比上一个大。
  • final关键字如何影响此行为?据我了解,如果所有对象字段都是final,那么它将以某种方式使构造函数成为原子的。在所有final字段都已初始化之前,不会发布参考。
  • 构造此类对象的最佳实践是什么?是否足以使lastRevision原子化或将newInstance声明为synchronized

2 个答案:

答案 0 :(得分:2)

  

说所有对象都将被构造是绝对正确的   一一贯地

不。 lastRevision.incrementAndGet()是一个阻塞调用,但是调度程序可以在接收到第一个ID之后暂停第一个线程,然后在第二个线程接收到第二个ID之后恢复它,这意味着两个构造函数将同时执行。

  

换句话说,每个新对象的时间戳都大于   上一个。

不,请参见上方。

  

final关键字如何影响此行为?

不是。

  

据我了解,如果所有对象字段都是最终字段,那么   原子构造器

不正确。如果每个字段都是final,则类是不可变的*,这使其隐式具有线程安全性。

  

构造此类对象的最佳实践是什么?足以使   应该lastRevision原子还是newInstance被声明为已同步?

如果必须依次创建每个实例,则newInstance应该同步。一旦存在,原子整数就没有意义了。即使执行此操作,时间戳也可能仍然相同,具体取决于底层系统时钟的分辨率。


*好吧,不完全是。如果每个字段都是最终字段,并且本身也是不可变的

答案 1 :(得分:1)

  • 原子性保证仅对incrementAndGet()调用有效。因此,是的,每个新对象的修订都是连续的,这是原子数据类型的目的。因此,回答您的问题:不能保证,多个线程将以与调用incrementAndGet()相同的顺序执行构造函数中的语句。为此,您必须将此部分放在synchronized块中。

  • final在这里没有帮助。它们是纯粹的逻辑功能,不允许在对象创建后对字段进行变异。

  • 如果您确实需要使时间戳记与修订版保持一致,则必须引入某种形式的线程同步。您可以使整个方法同步,然后lastRevision不必是原子的。

此外,无论您决定做什么,您可能还希望检查System.nanoTime()本身提供的担保。 Click