问题
The project is written using Play framework
and Scala
language.
I have implemented compile time dependency
.
I have followed this example from Play:
https://github.com/playframework/play-scala-compile-di-example
Looking at the 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)
}
and the following line of code:
lazy val homeController = new _root_.controllers.HomeController(controllerComponents)
my understanding is that there is only one instance of HomeController
created the first time HomeController
is called.
And that instance lives as long as the application lives. Are these statements correct?
The HomeController
in my application looks like that:
class HomeController{
val request = // some code here
val workflowExecutionResult = Workflow.execute(request)
}
So Workflow
is of type object
and not class
.
The Workflow
looks like that:
object Workflow {
def execute(request: Request) = {
val retrieveCustomersResult = RetrieveCustomers.retrieve()
// some code here
val createRequestResult = CreateRequest.create(request)
// some code here
workflowExecutionResult
}
}
So Workflow
calls a few domain services and each domain service is of type object
and not class
.
All values inside the domain services are immutable, I am using val
s everywhere.
Is this enough to ensure thread safety?
I am asking as I'm used to writing C# Web APIs
where a HomeController
would look like that:
class HomeControllerInSeeSharpProject{
// some code here
var request = new Request() // more code here
var workflow = new WorkflowInSeeSharpProject()
var workflowExecutionResult = workflow.execute(request)
}
and a Workflow
would look like that:
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
}
}
So in a C# project every time a HomeControllerInSeeSharpProject
is called a new instance of WorkflowInSeeSharpProject
is created and all the domain services
are also newed-up and then I can be sure that state cannot be shared between separate threads. So I am afraid that because my Scala
Workflow
and domain services are of type object
and not class
that there could be a situation where two requests are sent into the HomeController
and state is shared between those two threads.
Can this be the case? Is my application not thread safe?
I have read that object
s in Scala are not thread safe since there is only single instance of them. However I have also read that although
they are not thread safe using val
s will make the application thread safe...
Or maybe Play
itself has a way to deal with that problem?
回答1:
Because your are using compile time dependency injection, you control the number of instances created, and in your case HomeController
is created only once. As requests come in, this single instance will be shared between threads so indeed you have to make sure it is thread-safe. All the dependencies of HomeController
will also need to be thread-safe, thus object Workflow
has to be thread-safe. Currently, Workflow
is not publicly exposing any shared state, so it is thread-safe. In general, val
definitions within object
are thread-safe.
In effect HomeController
is behaving like a singleton and avoiding singletons could be safer. For example, by default Play Framework uses Guice dependency injection which creates a new controller instance per request as long as it is not a @Singleton
. One motivation is there is less state to worry about regarding concurrency protection as suggested by Nio's answer:
In general, it is probably best to not use @Singleton unless you have a fair understanding of immutability and thread-safety. If you think you have a use case for Singleton though just make sure you are protecting any shared state.
来源:https://stackoverflow.com/questions/50619826/play-scala-and-thread-safety