Что такое модель акторов и как она реализована в Java?
Модель акторов (Actor Model) — это математическая модель параллельных вычислений (Карл Хьюитт, 1973), в которой актор является базовой единицей вычислений. Каждый актор:
- Получает сообщения и обрабатывает их по одному
- Может создавать новых акторов
- Может отправлять сообщения другим акторам
- Может изменять своё внутреннее состояние
Ключевая идея: акторы никогда не разделяют состояние. Вся коммуникация — через асинхронную передачу сообщений. Это исключает проблемы синхронизации, deadlock и race condition по своей природе.
Основные принципы:
- Каждый актор имеет «почтовый ящик» (mailbox) — очередь входящих сообщений.
- Сообщения обрабатываются последовательно — внутри актора не нужна синхронизация.
- Отправка сообщений асинхронна и неблокирующая.
- Акторы идентифицируются по адресу (ActorRef), а не по ссылке на объект.
Реализации в Java:
1. Akka/Pekko — наиболее известная реализация
Код: актор на Akka
public class GreeterActor extends AbstractActor {
private int greetingCount = 0; // Состояние — безопасно без синхронизации
@Override
public Receive createReceive() {
return receiveBuilder()
.match(String.class, name -> {
greetingCount++;
getSender().tell("Привет получен!", getSelf());
})
.match(GetCount.class, msg -> {
getSender().tell(greetingCount, getSelf());
})
.build();
}
public static Props props() {
return Props.create(GreeterActor.class);
}
}
// Использование
ActorSystem system = ActorSystem.create("mySystem");
ActorRef greeter = system.actorOf(GreeterActor.props(), "greeter");
greeter.tell("Мир", ActorRef.noSender()); // fire-and-forget
Примечание: Akka изменил лицензию на BSL в 2022 году. Форк Apache Pekko продолжает развитие под Apache 2.0.
2. Упрощённая реализация на Virtual Threads:
Код: простой актор на BlockingQueue + Virtual Thread
public abstract class SimpleActor {
private final BlockingQueue<Object> mailbox = new LinkedBlockingQueue<>();
private final Thread thread;
protected SimpleActor() {
this.thread = Thread.ofVirtual().start(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Object message = mailbox.take();
onReceive(message);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
}
protected abstract void onReceive(Object message);
public void send(Object message) {
mailbox.offer(message);
}
public void stop() {
thread.interrupt();
}
}
// Конкретный актор
class CounterActor extends SimpleActor {
private int count = 0;
@Override
protected void onReceive(Object message) {
if (message instanceof String cmd) {
switch (cmd) {
case "increment" -> count++;
case "get" -> System.out.println("Count: " + count);
}
}
}
}
Сравнение с традиционной многопоточностью:
| Характеристика | Shared State | Actor Model |
|---|---|---|
| Состояние | Общее, защищённое блокировками | Изолированное внутри актора |
| Коммуникация | Через общую память | Через сообщения |
| Синхронизация | Явная (synchronized, Lock) | Не нужна |
| Deadlock | Возможен | Невозможен (нет блокировок) |
| Масштабирование | Сложно | Естественно |
Обработка ошибок — принцип «let it crash»: если актор упал, его супервизор (supervisor) решает: перезапустить актор, эскалировать ошибку или остановить его.
Частые ошибки:
- Передача изменяемых объектов как сообщений — нарушает изоляцию. Сообщения должны быть иммутабельными.
- Блокирующие вызовы внутри актора (
future.get()) — блокирует обработку всех сообщений. Patterns.askповсеместно вместоtell— снижает производительность.
Актуальность: Apache Pekko — основная реализация. Модель акторов используется в IoT, игровых серверах, финансовых системах. С Virtual Threads простые сценарии можно реализовать без тяжёлых фреймворков.
Аналогия: модель акторов — это организация работы через записки. Каждый сотрудник (актор) сидит в своём кабинете (изолированное состояние), получает записки (сообщения) во входящий лоток (mailbox) и обрабатывает их по одной. Никто не заходит к нему в кабинет и не трогает его документы — общение только через записки.
На собеседовании покажите понимание trade-offs: модель акторов устраняет проблемы разделяемого состояния, но усложняет отладку (асинхронные сообщения), тестирование и добавляет накладные расходы на сериализацию. Для Java-проектов без кластерного распределения Virtual Threads + Structured Concurrency часто являются более простой альтернативой.