Если вы все еще используете Synchronized, попробуйте вместо этого Akka Actor – вот почему

1656542290 esli vy vse eshhe ispolzuete synchronized poprobujte vmesto etogo akka

Мартин Буди

2kMFYzDOxojZGCZ0RcaCjDJ2kHwa0SC1nF7w

Синхронизировано является традиционным механизмом параллельности Java. Хотя сегодня это, вероятно, не то, что мы видим часто, оно все еще питает множество библиотек. Проблема в том, что синхронизация одновременно блокируется и сложна. В этой статье я хотел бы просто проиллюстрировать проблему и свои соображения по поводу перехода на Akka Actor для лучшего и более легкого параллельного использования.

Рассмотрим этот простой код:

  int x; 
 
 if (x > 0) {
   return true;
 } else {
   return false;
 }

Итак, верните true if x является положительным. Просто.

Далее рассмотрим этот еще более простой код:

x++;

Да, счетчик. Очень просто, верно?

Однако все эти коды могут потрясающе взорваться в многопоточной среде.

В первом примере значение true либо false не определяется значением x. Фактически это определяется посредством теста if. Итак, если другой поток сменит x на отрицательное сразу после того, как первый поток прошел тест if, мы все равно получим истину, даже если x больше не положительно.

Второй пример достаточно обманчив. Хотя это только одна строка, на самом деле есть три операции: чтение x, увеличивая его и возвращая обновленное значение. Если два потока запускаются одновременно, обновление может быть утрачено.

Когда у нас есть разные потоки, которые одновременно обращаются к переменной и меняют ее, у нас есть условие гонки. Если мы просто хотим построить счетчик, Java предоставляет потокобезопасные атомные переменные, в том числе атомные цели, которые мы можем использовать для этой цели. Однако Atomic Integer работает только с отдельными переменными. Как сделать несколько операций атомарными?

С помощью синхронизированные блокировать. Сначала рассмотрим более подробный пример.

int x; 
public int withdraw(int deduct){
    int balance = x - deduct; 
    if (balance > 0) {
      x = balance;
      return deduct;
    } else {
      return 0;
    }
}

Это очень простой способ снятия наличных денег. Это тоже бывает опасно. Выполняемые одновременно два потока могут привести к тому, что банк выдаст два снятия средств, даже если баланса больше не хватает. Теперь давайте посмотрим, как это работает с синхронизированным блоком:

volatile int x;
public int withdraw(int deduct){
  synchronized(this){
    int balance = x - deduct; 
    if (balance > 0) {
      x = balance;
      return deduct;
    } else {
      return 0;
    }
  }
}

Идея синхронизированного блока проста. Один поток входит в него и блокирует его, в то время как другие потоки ждут снаружи. В нашем случае замок – это объект это. После этого блокировка освобождается и передается другому потоку, который затем выполняет то же самое. Также обратите внимание на ключевое слово эзотерика непостоянный который необходим, чтобы поток не использовал локальный кэш ЦП x

Теперь, когда потоки распутаны, банк не будет случайно выдавать незаполненные средства. Однако эта структура, как правило, усложняется с большим количеством блоков и блоков. Работа с несколькими замками особенно рискованна. Блоки могут ненамеренно удерживать ключ друг для друга и в конечном итоге заблокировать всю программу. Кроме того, у нас есть проблема эффективности. Помните, что пока поток работает внутри, все остальные потоки ждут снаружи. И ожидают потоки хорошо… ждут. Они не делают ничего другого, кроме как ждут.

Так что вместо того, чтобы делать такой механизм, почему бы просто не оставить работу в очереди? Чтобы лучше это представить, представьте систему электронной почты. При отправке электронного письма вы бросаете письмо в почтовый ящик получателя. Вы не ждете, пока человек это прочтет.

Это основы модели Actor и фреймворка Akka в целом.

Актер инкапсулирует состояние и поведение. В отличие от инкапсуляции ООП, актеры вообще не разоблачают свое состояние и поведение. Единственный способ для актеров общаться друг с другом – обмен сообщениями. Входящие сообщения сбрасываются в почтовый ящик и перевариваются в порядке «первым пришел – первым вышел». Вот переделанный образец в Akka и Scala.

case class Withdraw(deduct: Int)
class ReplicaActor extends Actor {
  var x = 10;
  def receive: Receive = {
    case Withdraw(deduct) => val r = withdraw(deduct)
  }
}
class BossActor extends Actor {
  var replica = context.actorOf(Props[ReplicaActor])
  replica ! Withdraw(6)
  replica ! Withdraw(9)   
}

У нас есть ReplicaActor, который выполняет работу, и BossActor, упорядочивающий реплику. Во-первых, обратите внимание на ! знак или рассказать. Это один из двух методов (другой спроси), чтобы актер асинхронно посылал сообщение другому актеру. рассказать в частности, делает это, не дожидаясь ответа. Поэтому босс говорит реплике выполнить два приказа об отзыве и немедленно идет. Эти сообщения поступают в реплику получать где каждый из них появляется и совпадает с подходящим обработчиком. В этом случае, Отозвать выполняет отзывать метод из предыдущего примера и вычитает запрошенную сумму из состояния x. После этого актер переходит к следующему сообщению в очереди.

Итак, что мы здесь получаем? Во-первых, нам больше не нужно беспокоиться о блокировании и работе с атомарными/конкурентными типами. Механизм инкапсуляции и очереди актера уже обеспечивает безопасность потоков. И больше не нужно ждать, поскольку потоки просто сбрасывают сообщения и возвращаются. Результаты можно предоставить позже с помощью спроси или рассказать. Это просто и разумно.

Akka основана на JVM и доступна как в Scala, так и в Java. Хотя эта статья не обсуждает Java против Scala, согласование шаблонов и функциональное программирование Scala были бы очень полезны для управления сообщениями данных Actor. По крайней мере, это может помочь вам писать более короткий код, избегая скобок и точки с запятой Java.

Добавить комментарий

Ваш адрес email не будет опубликован.