如何在AKKA Actor中保持线程安全?

时间:2020-06-30 15:00:49

标签: java multithreading concurrency thread-safety akka

我的项目需要大量的异步编程,因此我选择AKKA平台,因为actor模型可以像编写同步代码一样实现异步系统,而不必担心线程问题。一切正常,直到遇到以下问题(演示代码)为止:

import akka.actor.AbstractActor;
import akka.japi.pf.ReceiveBuilder;
import java.util.concurrent.locks.ReentrantLock;

public class TestActor extends AbstractActor {
    private final ReentrantLock lock  = new ReentrantLock();

    @Override
    public Receive createReceive() {
        return ReceiveBuilder.create()
                .matchEquals("lock", s -> lock.lock())
                .matchEquals("unlock", s -> lock.unlock())
                .build();
    }
}

首先发送“锁定”消息,然后发送“解锁”消息,试图在接收到发送消息后进行解锁,则抛出IllegalMonitorStateException,我发现这是由于存在不同的消息s -> lock.lock()s -> lock.unlock()实际上是由不同的线程处理的,因此它们在不同的线程中执行,因此会抛出IllegalMonitorStateException

我以前的假设是,actor的所有动作都是在一个线程中执行的,因此它是完全线程安全的,不必担心线程问题。当我在项目中广泛使用AKKA时,现在我很担心并且不清楚剂量何时需要在使用AKKA时考虑线程问题。例如,在以下演示代码中:

public class TestActor1 extends AbstractActor {
    private int count  = 0;
    private Map<Integer, Integer> map = new HashMap<>();
    
    @Override
    public Receive createReceive() {
        return ReceiveBuilder.create()
                .matchEquals("action1", s -> count++)
                .matchEquals("action2", s -> getSender().tell(count, getSelf()))
                .matchEquals("action3", s -> map.put(3, 2))
                .matchEquals("action4", s -> getSender().tell(map.get(3), getSelf()))
                .build();
    }
}

使用countmap的方式是线程安全的吗?我需要为volatile使用count并为ConcurrentHashMap使用map吗?

ps ====================

以下演示代码演示了为什么我需要锁定actor,基本上我正在实现带有反压控制的管道,一旦actor从上游actor接收到太多任务,它将向上游发送backPressureHi消息演员停止上游演员执行循环,直到背压恢复正常并发送backPressureNormal来恢复:

public class PipeLineActor extends AbstractActor {
    private final ReentrantLock stallLock = new ReentrantLock();

    private Thread executionLoop = new Thread(() -> {
        while (true){
            stallLock.lock();
            stallLock.unlock();
            
            // issue tasks to down stream actors
        }
    });

    @Override
    public Receive createReceive() {
        return ReceiveBuilder.create()
                // down stream actor send "backPressureHi" when back pressure is high to stall the executionLoop
                .matchEquals("backPressureHi", s -> stallLock.lock())
                // down stream actor send "backPressureNormal" when back pressure resumed normal to resume the executionLoop
                .matchEquals("backPressureNormal", s -> stallLock.unlock())
                .build();
    }
}

1 个答案:

答案 0 :(得分:2)

Akka被设计为线程安全的。而且,从不需要演员之间的锁定或同步。不应该这样做。

Akka通过一次处理一条消息来实现线程安全。角色无法同时处理多个消息。但是消息可能并且将在不同的线程中进行处理。 (这是默认行为,但例如可以通过pin分配器进行更改。)

来自documentation

不需要诸如并发或AtomicInteger之类的并发防护 因为一个actor实例一次处理一条消息。

对于您最后的问题,

使用线程计数和映射的方式安全吗?

是的,它是线程安全的。

我是否需要使用volatile进行计数并使用ConcurrentHashMap进行地图?

没有,没有必要这样做。参见Akka and the Java Memory Model

用外行的话来说,这意味着对 当该actor处理下一条消息时,该actor可见。所以 actor中的字段不必是可变的或等效的。