在构造函数中同步块是否必要?

huangapple go评论120阅读模式
英文:

Is it necessary to synchronize blocks in a constructor?

问题

这个问题没有回答必要部分:
https://stackoverflow.com/questions/7993874/synchronized-blocks-in-constructors.

鉴于这个最大限度

  • JVM 不会允许多于一个线程同时调用一个类的构造函数

问题 A

这是否意味着...?

  1. JVM 不会允许多于一个线程调用一个类的任何构造函数
    • 即使一个类有多个构造函数,该类一次只能由一个线程实例化。
  2. 如果一个类有多个构造函数,则每个构造函数一次只能被一个线程调用
    • 即使一个类有三个构造函数,那么三个线程也可以同时实例化该类。

对我来说,#1 逻辑上是问题 1 的答案。

问题 B

如果问题 A 的答案 #1 是真的,这是否意味着在构造函数中使用同步块没有意义?

例如,假设 setBrandAccessor 方法只被构造函数调用,那么是否不需要在 setBrandAccessor 方法中使用同步?如果有必要,请解释为什么。

class DataAccessor {
  static Brand brandAccesor;

  DataAccessor(Brand brand) {
    super(brand);
    setBrandAccessor(Brand brand);
  }
  
  private synchronized setBrandAccessor(Brand brand) {
    if (brandAccessor==null) brandAccessor=brand;
  }
}
英文:

This question does not answer the necessity part:
https://stackoverflow.com/questions/7993874/synchronized-blocks-in-constructors.

Given this maxim

  • JVM will not allow more than one thread to call the constructor of a class at a time

Question A

Does it mean .... ?

  1. JVM will not allow more than one thread to call any constructor of a class
    • such that even if there are more than one constructors in a class, a class can be instantiated by only one thread at a time.
  2. If a class has more than one constructors, then each constructor can be called by no more than one thread at a time
    • such that if a class has three constructors, then three threads could simultaneously instantiate that class.

It seems logical to me that #1 is the answer to Question 1.

Question B

If QuestionA.answer#1 is true, does it mean it is pointless to have synchronized blocks in a constructor?

For example, presuming setBrandAccessor method is called only by the constructor, is it unnecessary to synchronize the setBrandAccessor method? If necessary, please explain why.

class DataAccessor {
  static Brand brandAccesor;

  DataAccessor(Brand brand) {
    super(brand);
    setBrandAccessor(Brand brand);
  }
  
  private synchronized setBrandAccessor(Brand brand) {
    if (brandAccessor==null) brandAccessor=brand;
  }
}

答案1

得分: 4

> JVM不会允许多个线程同时调用一个类的构造函数。

这不是真实情况,多个线程可以同时调用构造函数。我不知道你从哪里得到这个观点。

问题 A

都不正确,这个观点本身就是错误的。

问题 B

它不会产生你认为的效果。setBrandAccessor 方法会在 this 上同步,而对于每个构造函数来说,this 都是不同的,所以该方法不会按照你期望的方式进行同步,实际上就相当于没有同步。你可以像这样做:

class DataAccessor {
  static Object lock = new Object();
  static Brand brandAccessor;

  DataAccessor(Brand brand) {
    super(brand);
    synchronized(lock) {
        if (brandAccessor == null) brandAccessor = brand;
    }
  }
}

或者

class DataAccessor {
  static Brand brandAccessor;

  DataAccessor(Brand brand) {
    super(brand);
    setBrandAccessor(brand);
  }
  
  // 静态同步与在静态对象上同步相同
  private static synchronized setBrandAccessor(Brand brand) {
    if (brandAccessor == null) brandAccessor = brand;
  }
}

不过,这似乎有点不合适的模式。如果没有更多的上下文,很难发表评论,但我会感到意外,如果你不能将代码结构得比这更好。

英文:

> JVM will not allow more than one thread to call the constructor of a class at a time

This is not true, multiple threads can call constructors at the same time. I don't know where you got this maxim.

Questions A

Neither is true, the maxim itself is incorrect.

Question B

This does not do what you think it will do. setBrandAccessor would be synchronized on this, which is different for each constructor, so the method would not be synchronized in the way you expect it to be, and would de-facto be the same as not synchronizing it at all. You could do something like this:

class DataAccessor {
  static Object lock = new Object();
  static Brand brandAccesor;

  DataAccessor(Brand brand) {
    super(brand);
    synchronized(lock) {
        if (brandAccessor==null) brandAccessor=brand;
    }
  }
}

or

class DataAccessor {
  static Brand brandAccesor;

  DataAccessor(Brand brand) {
    super(brand);
    setBrandAccessor(Brand brand);
  }
  
  // Being static synchronized is the same as synchronizing on a static object
  private static synchronized setBrandAccessor(Brand brand) {
    if (brandAccessor==null) brandAccessor=brand;
  }
}

That does seem like a bit of an antipattern though. I'd be surprised if you can't structure your code better than this, but it's hard to comment without more context.

答案2

得分: 0

Question A: 两个线程可以同时构造一个对象,同时运行构造函数。但两个线程无法同时构造同一个对象,它们会构造两个不同的对象。构造函数在两个不同的对象上运行,这消除了对于同步块的最大需求。

Question B: 构造函数中的同步块并非毫无意义:构造函数可能会访问其他类中的可变共享状态或静态变量,同步块是确保互斥性的一种方式。

关于setBrandAccessor(请注意,我已经移除了static修饰符,使该字段成为实例字段):

class DataAccessor {
  Brand brandAccesor;

  DataAccessor(Brand brand) {
    super(brand);
    setBrandAccessor(brand);
  }
  
  private synchronized setBrandAccessor(Brand brand) {
    brandAccessor = brand;
  }
}

如果你从其他线程使用此类的对象,可能需要将该方法设置为synchronized。你需要确保在其他线程中的字段读取之前有一个“happens before”边界,无论这个“写入”是否发生在构造函数中。将getset方法都设置为synchronized可以保证适当的多线程可见性。

另一种确保多线程可见性的方式是移除set方法并将字段设置为final

  final Brand brandAccesor;

  DataAccessor(Brand brand) {
    super(brand);
    brandAccessor = brand;
  }
英文:

Question A: Two threads can construct an object at the same time, running the constructor in parallel. What two threads can't do is construct the same object at the same time. The two threads will construct two different objects. The constructors run on two different objects, which removes one of the biggest needs for synchronized blocks.

Question B: synchronized blocks in constructors are not pointless: A constructor may access mutable shared state in other classes, or in static variables, and synchronized blocks are one way to guarantee mutual exclusion.

Regarding setBrandAccessor in (note that I have removed the static modifier, making the field an instance field):

class DataAccessor {
  Brand brandAccesor;

  DataAccessor(Brand brand) {
    super(brand);
    setBrandAccessor(brand);
  }
  
  private synchronized setBrandAccessor(Brand brand) {
    brandAccessor=brand;
  }
}

If you're using objects of this class from other threads, it may be necessary to make the method synchronized. You need to guarantee there's a "happens before" edge between the field being written and it being read in the other thread regardless whether the "write" happens in the constructor. Making the get and set methods synchronized guarantees proper multithreading visibility.

Another way to guarantee multithreading visibility is to remove the set method and making the field final.

  final Brand brandAccesor;

  DataAccessor(Brand brand) {
    super(brand);
    brandAccessor=brand;
  }


</details>



huangapple
  • 本文由 发表于 2020年8月21日 08:21:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/63514866.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定