Статьи

Akka STM — игра в пинг-понг с игроками и агентами STM

PingPong — классический пример, когда 2 игрока (или потоки) получают доступ к общему ресурсу — PingPong Table и передают мяч (переменную состояния) между собой. С любым общим ресурсом, если мы не синхронизируем доступ, потоки могут столкнуться с потенциальной тупиковой ситуацией.

Алгоритм PingPong очень прост

если моя очередь {
обновить чья очередь
пинг / понг -лог хит
уведомить другие темы
} еще {
ждать уведомления
}

Давайте рассмотрим пример и посмотрим, как это работает! Вот наш класс Player, который реализует Runnable и принимает доступ к общему ресурсу и сообщению

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class Player implements Runnable {
 PingPong myTable;  Table where they play
 String myOpponent;
 
 public Player(String opponent, PingPong table) {
  myTable = table;
  myOpponent = opponent;
 }
 
 public void run() {
  while (myTable.hit(myOpponent))
   ;
 }
 
}

Во-вторых, мы видим класс таблицы PingPong, у которого есть синхронизированный метод hit (), где выполняется проверка, если мой ход или нет. Если мой ход, войдите в пинг и обновите общую переменную для имени оппонента.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class PingPong {
  state variable identifying whose turn it is.
 private String whoseTurn = null;
 
 public synchronized boolean hit(String opponent) {
 
  String x = Thread.currentThread().getName();
 
  if (x.compareTo(whoseTurn) == 0) {
   System.out.println('PING! (' + x + ')');
   whoseTurn = opponent;
   notifyAll();
  } else {
  try {  wait(2500); }
  catch (InterruptedException e) { }
  }
  }
}

Далее мы начинаем игру и начинаем игроков!

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Game {
 public static void main(String args[]) {
  PingPong table = new PingPong();
  Thread alice = new Thread(new Player('bob', table));
  Thread bob = new Thread(new Player('alice', table));
 
  alice.setName('alice');
  bob.setName('bob');
  alice.start();  alice starts playing
  bob.start();  bob starts playing
  try {
    Wait 5 seconds
   Thread.sleep(5000);
  } catch (InterruptedException e) {
  }
 
  table.hit('DONE');  cause the players to quit their threads.
  try {
   Thread.sleep(100);
  } catch (InterruptedException e) {
  }
 }
}

Вот и все, у нас запущена игра в пинг-понг. В этом случае мы увидели, как синхронизированный метод hit () позволяет только одному потоку обращаться к общему ресурсу — whichTurn.

Akka STM предоставляет две конструкции Refs и Agents. Refs (Transactional References) обеспечивают согласованный синхронный доступ к нескольким удостоверениям. Агенты предоставляют несогласованный асинхронный доступ к единому идентификатору.

Refs

В нашем случае, поскольку переменная состояния общего ресурса является единым идентификатором, использование Refs является излишним, но все же мы продолжим и увидим их использование.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class PingPong {
 
 updates to Ref.View are synchronous
 Ref.View<String> whoseTurn;
 
 public PingPong(Ref.View<String> player) {
  whoseTurn = player;
 }
 
 public boolean hit(final String opponent) {
  final String x = Thread.currentThread().getName();
 
  if (x.compareTo(whoseTurn.get()) == 0) {
   System.out.println('PING! (' + x + ')');
   whoseTurn.set(opponent);
  } else {
   try {
    wait(2500);
          } catch (Exception e) {
   }
  }
}
}

Ключ здесь следующие

  • Синхронизированное ключевое слово отсутствует
  • Определение переменной состояния как Ref
    // обновления в Ref.View являются синхронными
    Ref.View <string> whoTurn;
  • Звонки для обновления Ref согласованы и синхронны
    whoTurn.set (противник) ;

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

Агенты

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class PingPong {
 
 Agent<String> whoseTurn;
 
 public PingPong(Agent<String> player) {
  whoseTurn = player;
 }
 
 public boolean hit(final String opponent) {
  final String x = Thread.currentThread().getName();
 
  wait till all the messages are processed to make
  you get the correct value, as updated to Agents are
  async
  String result = whoseTurn.await(new Timeout(5, SECONDS));
 
  if (x.compareTo(result) == 0) {
   System.out.println('PING! (' + x + ')');
   whoseTurn.send(opponent);
  } else {
   try {
    wait(2500);
 
   } catch (Exception e) {
   }
  }
  return true;  keep playing.
 }
}

Ключ здесь следующие

  • Синхронизированное ключевое слово отсутствует
  • Определение переменной состояния в качестве агента
    // обновления в Ref.View являются синхронными
    Агент <string> whoTurn;
  • Дождитесь обновления агента, поскольку обновления агента являются асинхронными
    String result = whoTurn.await (новый тайм-аут (5, СЕКУНД));
  • Звонки для обновления Ref согласованы и синхронны
    whoTurn.send (противник) ;

Весь код, указанный в этом примере, доступен по адресу — https://github.com/write2munish/Akka-Essentials/tree/master/AkkaSTMExample/src/main/java/org/akka/essentials/stm/pingpong с
Пример 1 — для нормальной синхронизации на основе потоков
Пример 2 — Использование ссылок для синхронизации
Пример 3 — Использование Агентов для синхронизации

Справка: игра в пинг-понг с STM — ссылками и агентами от нашего партнера по JCG Муниша К Гупты в блоге Akka Essentials .