使用Scala编程语言并保证线程安全性

5
该项目使用Play框架Scala语言编写。 我实现了编译时依赖。 我按照Play的这个例子进行操作:

https://github.com/playframework/play-scala-compile-di-example

查看MyApplicationLoader.scala:
import play.api._
import play.api.routing.Router

class MyApplicationLoader extends ApplicationLoader {
  private var components: MyComponents = _

  def load(context: ApplicationLoader.Context): Application = {
    components = new MyComponents(context)
    components.application
  }
}

class MyComponents(context: ApplicationLoader.Context) 
  extends BuiltInComponentsFromContext(context)
  with play.filters.HttpFiltersComponents
  with _root_.controllers.AssetsComponents {

  lazy val homeController = new _root_.controllers.HomeController(controllerComponents)

  lazy val router: Router = new _root_.router.Routes(httpErrorHandler, homeController, assets)
}

以下是一行代码:

 lazy val homeController = new _root_.controllers.HomeController(controllerComponents)

我的理解是,在第一次调用 HomeController 时,只会创建一个实例。 而且这个实例的生命周期跟应用程序相同。这些说法正确吗?
我的应用程序中的 HomeController 如下所示:
class HomeController{

   val request = // some code here

   val workflowExecutionResult = Workflow.execute(request)

}

因此,Workflow是一种类型为object而不是class的对象。

Workflow的样子如下:

object Workflow {
  def execute(request: Request) = {

    val retrieveCustomersResult = RetrieveCustomers.retrieve() 
    // some code here

    val createRequestResult = CreateRequest.create(request)
    // some code here

    workflowExecutionResult
  }
}

所以 Workflow 调用了一些领域服务,每个领域服务都是 object 类型而不是 class

所有领域服务内部的值都是不可变的,在代码中使用了 val

这样是否足以确保线程安全?

我问这个问题是因为我习惯于编写 C# Web APIs,在那里一个HomeController 看起来会像这样:

class HomeControllerInSeeSharpProject{

    // some code here

    var request = new Request() // more code here
    var workflow = new WorkflowInSeeSharpProject()
    var workflowExecutionResult = workflow.execute(request)
}

一个 Workflow 的样子如下:

public class WorkflowInSeeSharpProject {

  public execute(Request request) {

      var retrieveCustomers = new RetrieveCustomers()
      var retrieveCustomersResult = retrieveCustomers.retrieve()

      // some code here
      var createRequest = new CreateRequest()
      var createRequestResult = createRequest.create(request)

      // some code here
      return workflowExecutionResult
  }
}

在C#项目中,每次调用HomeControllerInSeeSharpProject时,都会创建一个新的WorkflowInSeeSharpProject实例,并且所有的领域服务也都是新的,因此我可以确保状态不能在不同线程之间共享。但是我担心,由于我的Scala Workflow和领域服务是object类型而不是class类型,可能会出现两个请求发送到HomeController并且状态在这两个线程之间共享的情况。
这种情况是否可能发生?我的应用程序是否不安全?
我已经了解到,在Scala中,object不是线程安全的,因为它们只有一个实例。然而,我也读到过,尽管它们不是线程安全的,使用val可以使应用程序变得线程安全...
或者,Play本身有处理这个问题的方法吗?
1个回答

1
因为您正在使用编译时依赖注入,所以您可以控制创建的实例数量,在您的情况下,HomeController仅被创建一次。随着请求的到来,这个单一的实例将在线程之间共享,因此确保它是线程安全的非常重要。HomeController的所有依赖项也需要是线程安全的,因此object Workflow必须是线程安全的。目前,Workflow没有公开任何共享状态,因此它是线程安全的。通常,在object中的val定义是线程安全的
实际上,HomeController的行为类似于单例模式,避免使用单例可能更安全。例如,默认情况下,Play框架使用Guice依赖注入,只要不是@Singleton,每个请求都会创建一个新的控制器实例。其中一个动机是关于并发保护方面有更少的状态需要担心,正如Nio's answer所建议的那样:

一般来说,最好不要使用@Singleton,除非你对不变性和线程安全有相当的了解。如果你认为你有一个使用单例的用例,确保你保护任何共享状态。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接