Делает ли синхронизация в поле stati c, которое вы изменяете, ваш поток кода безопасным? - PullRequest
0 голосов
/ 27 февраля 2020
class A {
  private static BigInteger staticCode = BigInteger.ZERO;
  private BigInteger code;
  public A() {
    synchronized(staticCode) {
      staticCode = staticCode.plus(BigInteger.ONE);
      code = staticCode;
    }
  }

 }

Я ни в коем случае не эксперт по параллелизму. Может кто-нибудь объяснить мне, почему приведенный выше класс не является поточно-ориентированным?

В каких ситуациях может возникнуть состояние гонки? Мой мыслительный процесс состоит в том, что если мы создадим 10 экземпляров этого класса, каждый экземпляр будет синхронизироваться с другим значением staticCode, и поэтому он безопасен для потоков, но мне сказали, что это не так. Но почему?

Я знаю, что мы можем синхронизироваться на .class, и это определенно будет поточно-ориентированным, но я все еще хочу разобраться в этой конкретной ситуации.

Ответы [ 2 ]

9 голосов
/ 27 февраля 2020

Делает ли синхронизация в поле stati c, которое вы изменяете, ваш поток кода безопасным?

Нет, потому что вы переназначаете его. (*)

Как только это переназначение произошло, вы фактически утратили взаимное исключение при доступе к полю staticCode.

  • Любой поток, который уже ожидает в блоке synchronized до того, как назначение продолжит ждать.
  • Любой поток, который поступит в блок synchronized после переназначения, но до того, как переназначенный поток покинул блок, попытается синхронизироваться по новое значение staticCode.

    Более тонкий момент, чем тот факт, что у вас нет взаимного исключения, заключается в том, что вы также теряете случайность перед концом синхронизированного блока. и начало следующего исполнения. Это означает, что у вас нет гарантированной видимости обновленного значения, поэтому вы можете потенциально сгенерировать несколько экземпляров A с одним и тем же code.

Это плохая идея синхронизировать на не финальном члене. Если вы не хотите синхронизироваться на A.class, вы можете определить вспомогательный элемент для синхронизации:

class A {
  private static final Object lock = new Object();
  private static BigInteger staticCode = BigInteger.ZERO;

  public A() {
    synchronized (lock) {
      staticCode = ...
    }
  }
}

Это сохраняет изменчивость staticCode, но допускает корректное взаимное исключение.

Однако класс Atomic* будет намного проще, потому что вы избегаете необходимости синхронизации (например, AtomicInteger или AtomicLong - но если вы действительно думаете, что у вас будет более 2 ^ 63 вещей, Вы можете использовать AtomicReference<BigInteger>):

class A {
  private static final Object lock = new Object();
  private static AtomicReference<BigInteger> staticCode = new AtomicReference<>(BigInteger.ZERO);

  public A() {
    BigInteger code;
    do {
      code = staticCode.get();
    } while (!staticCode.compareAndSet(code, code.add(BigInteger.ONE)));
    this.code = code;

    // Even easier with AtomicInteger/Long:
    // this.code = BigInteger.valueOf(staticCode.incrementAndGet());
  }
}

(*) Но в любом случае, не думайте, что синхронизация автоматически делает что-то поточно-безопасным. Во-первых, вам нужно точно определить, что вы подразумеваете под «потокобезопасностью»; но затем вам необходимо понять, что на самом деле обеспечивает синхронизация, чтобы оценить, удовлетворяют ли эти требования вашим требованиям безопасности потоков.

1 голос
/ 27 февраля 2020

Полагаю, главное, чего я здесь упустил, это то, что мы синхронизируемся на объектах, а не на ссылках на объекты.

Рассмотрим ситуацию, когда я синхронизируюсь на BigInteger.ZERO, а затем вхожу в синхронизированный блок.

Когда значение staticCode изменилось и стало BigInteger.ONE, этот блок продолжает синхронизироваться с BigInteger.ZERO. Между тем другой поток уже синхронизирован на BigInteger.ONE, еще до того, как мы изменили назначение BigInteger.ONE для кода. Этот второй поток может увеличить staticCode до значения 2, и теперь оба потока находятся перед вторым назначением, но значение staticCode равно 2, поэтому они оба могут назначить одно и то же значение staticCode 2 различным экземплярам класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...