juan_gandhi: (Default)
Juan-Carlos Gandhi ([personal profile] juan_gandhi) wrote2007-04-11 02:48 pm

java synchronization gone wild and back

Hey, so, you, like everyone else, decided to make your static initialization thread-safe. You have a singleton that is hard to instantiate, and You have something like this:


static My worker;

static My worker() {
  if (worker == null) {
    worker = new MyImplementation();
  }
  return worker;
}


and it does not work, because, say, a dozen of servlet threads in parallel are trying to reinitialize your singleton like crazy; a minute that it takes to instantiate one, turns into 12 minutes, since all 12 are trying to do the same.

Okay, you synchronize it:


static My worker;

static synchronized worker() {
  if (worker == null) {
    worker = new MyImplementation();
  }
  return worker;
}


In this case you lock execution on the whole class; your class may have a bunch of other singletons, and they will all stand in line waiting for this specific singleton to be retrieved - looks stupid, right? so you introduce a gentle check:


static My worker;

static worker() {
  if (worker == null) {
    synchronized {
      worker = new MyImplementation();
    }
  }
  return worker;
}


See, before instantiating, you check whether it is instantiated already.

The trouble is, your twelve threads will get through this check and wait on the same barrier before synchronized block, and then will instantiate the singleton, one after another, taking 12 minutes in total. Okay, you get angry, you open a book and steal a solution, the famous double-check trick:


static My worker;

static worker() {
  if (worker == null) {
    synchronized {
      if (worker == null) {
        worker = new MyImplementation();
      }
    }
  }
  return worker;
}


Now you feel good, the threads would not reinitialize the same singleton, because when the second threads penetrates the synchronized block, worker is already not null. You feel good... but then you open Josh and Neal's Java Puzzlers, and see that oops, this won't work on a multiprocessor machine. Why? See, by the time the first thread leaves the syncrhonized block, the value of worker may not reach the memory shared by the threads.

So, the solution suggested is this:


static My worker;
private interface HR {
  My getWorker();
}

private static HR catbert = new HR() {
  My worker = new MyImplementation();
  My getWorker() {
    return worker;
  }
}

static worker() {
  return catbert.getWorker();
}


See, the trick is that the singleton is provided by a static class, which is loaded strictly once, which is guaranteed by the class loader.

Now enters "new Java 5", not sure which one, 1.5.0.6 or 1.5.0.7. Bug fixed. All you have to do is declare My worker volatile:


volatile static My worker;

static worker() {
  if (worker == null) {
    synchronized {
      if (worker == null) {
        worker = new MyImplementation();
      }
    }
  }
  return worker;
}


So, now we are back to circle 3: after a bugfix, double checks are legal again.

[identity profile] selfmade.livejournal.com 2007-04-12 06:10 pm (UTC)(link)
1. Я посмотрел MSIL для 2-го решения и 5-го. Во втором используется lock, собственно как это и видно из исходника. В пятом не используются локи, по крайней мере я не нашёл. Если я правильно понимаю, то вся хитрость в реализации CLR, которая гарантирует единственность выполнения статического конструктора на AppDomain (возможно эта реализация использует lock в своём unmanaged code, не знаю как проверить). Наверное стоило скомпилировать в native code и проверить так, но я не знаю assembler.

Производительность всегда можно померять.

2. В .NET инстанс статического класса создастся даже тогда, когда есть вызовы его статических членов. Если у нас есть статические члены не связанные по функциональности с нашим "синглтоном", то хотелось бы развязать вызовы этих членов и создании инстанса класса. С другой стороны понятно, что это кривизна дизайна.

3. Так этот вопрос совершенно не касается singleton. Возможно в этом случае lock является лучшим вариантом.

4. Согласен.

[identity profile] 109.livejournal.com 2007-04-12 06:29 pm (UTC)(link)
3. как не касается? решение, преподносимое как самое лучшее, будет работать только для "настоящего" синглтона, а для просто статического члена - всё равно надо double checking and all that jazz.

2. В .NET инстанс статического класса создастся даже тогда, когда есть вызовы его статических членов.

мнэ... huh?

0. на самом деле я, конечно, погорячился. просто обидно стало, что годами проверенный код вдруг так походя записали в bad. действительно интересно, чем обеспечивается единственность вызова статического констрактора - но даже если тем же локом или unmanaged аналогом, то реализация со вложенным классом всё равно мне больше нравится, несмотря даже на "лишний" элемент в скопе. а в джаве так и этого минуса нет, там вложенный класс виден внешнему, даже если он private.

[identity profile] 109.livejournal.com 2007-04-12 06:45 pm (UTC)(link)
3. я тормоз. действительно, и для обычного статического члена эту технику можно применить с таким же успехом.

[identity profile] selfmade.livejournal.com 2007-04-12 06:48 pm (UTC)(link)
Ага. А потом окажется, что CLR under the hood делает лок и мы оказываемся не только с локом, но и с лишним классом впридачу. :)

Что там народ из CLR team говорит по этому поводу?