Параметры Atomic compareAndSet оцениваются, даже если он не используется - PullRequest
1 голос
/ 04 июля 2019

У меня есть следующий код, который устанавливает переменную Atomic (и java.util.concurrent.atomic, и monix.execution.atomic ведут себя одинаково:

class Foo {
  val s = AtomicAny(null: String)

  def foo() = {
    println("called")
    /* Side Effects */ 
    "foo" 
  }

  def get(): String = {
    s.compareAndSet(null, foo())
    s.get
  }
}


val f = new Foo
f.get //Foo.s set from null to foo, print called
f.get //Foo.s not updated, but still print called

Во второй раз, когда он сравнивает AndSet, он не обновил значение, но все равно вызывается foo. Это вызывает проблему, потому что foo имеет побочные эффекты (в моем реальном коде он создает актера Akka и выдает ошибку, потому что он пытается создать дублирующих актеров).

Как я могу убедиться, что второй параметр не оценивается, если он на самом деле не используется? (Желательно не использовать синхронизированный)

Мне нужно передать неявный параметр в foo, чтобы lazy val не работал. Э.Г.

  lazy val s = get() //Error cannot provide implicit parameter

  def foo()(implicit context: Context) = {
    println("called")
    /* Side Effects */ 
    "foo" 
  }

  def get()(implicit context: Context): String = {
    s.compareAndSet(null, foo())
    s.get
  }

1 Ответ

1 голос
/ 04 июля 2019

Обновленный ответ

Быстрый ответ - поместить этот код в актера, и вам не нужно беспокоиться о синхронизации.

Если вы используете Akka Actors, вам никогда не нужно выполнять собственную синхронизацию потоков с использованием низкоуровневых примитивов. Весь смысл модели актора состоит в том, чтобы ограничить взаимодействие между потоками только передачей асинхронных сообщений. Это обеспечивает всю необходимую вам синхронизацию потоков и гарантирует, что субъект обрабатывает одно сообщение за раз однопоточным способом.

У вас определенно не должно быть функции, к которой одновременно могут обращаться несколько потоков, создающих одноактный актер. Просто создайте актера, когда у вас есть нужная информация, и передайте ActorRef любым другим акторам, которым это нужно, с помощью внедрения зависимости или сообщения. Или создайте актера в начале и инициализируйте его при поступлении первого сообщения (используйте context.become для управления состоянием актера).


Оригинальный ответ

Самое простое решение - просто использовать lazy val для хранения вашего экземпляра foo:

class Foo {
  lazy val foo = {
    println("called")
   /* Side Effects */ 
   "foo" 
  }
}

Это создаст foo при первом использовании и после этого просто вернет то же значение.

Если по какой-либо причине это невозможно, используйте AtomicInteger, инициализированный для 0, а затем позвоните incrementAndGet. Если это возвращает 1, то это первый проход через этот код, и вы можете позвонить foo.

Пояснение:

Атомарные операции, такие как compareAndSet, требуют поддержки из набора команд ЦП, и современные процессоры имеют отдельные атомарные инструкции для таких операций. В некоторых случаях (например, строка кэша поддерживается исключительно этим процессором) операция может быть очень быстрой. В других случаях (например, строка кэша также в кеше другого процессора) операция может быть значительно медленнее и влиять на другие потоки.

В результате процессор должен удерживать новое значение перед выполнением атомарной инструкции. Таким образом, значение должно быть вычислено до того, как станет известно, нужно ли оно или нет.

...