Inspired by this Brian Goetz article: Are all stateful Web applications broken?
Core classes: HttpServlet
, ServletContext
ServletContext
defines a set of methods that a servlet uses to communicate with its servlet container, for example, to get the MIME type of a file, dispatch requests, or write to a log file.
The ServletContext
object is contained within the ServletConfig
object, which the Web server provides to the servlet when the servlet is initialized.
Core class: HttpSession
The server can maintain a session in many ways such as using cookies or rewriting URLs.
Session information is scoped only to the current web application (ServletContext
), so information stored in one context will not be directly visible in another.
The HttpSession
interface acts as a value store for (key, value) data items relevant to that session.
This extends the ServletRequest
interface to provide request information for HTTP servlets.
The servlet container creates an HttpServletRequest
object and passes it as an argument to the servlet’s service methods (doGet, doPost, etc).
The ServletContext
, HttpSession
, and HttpRequest
objects in the Servlet specification are referred to as scoped containers.
Lifetime of scoped containers:
Container | Scope |
---|---|
HttpRequest |
lifetime of the request |
HttpSession |
lifetime of a session between a user and the application |
ServletContext |
lifetime of the application |
For the last 2, getAttribute()
and setAttribute()
methods may be called at any time by different threads.
When an HTTP request arrives at the servlet container, HttpRequest
and HttpResponse
objects are created and passed to the service()
method of a servlet, in the context of a thread managed by the servlet container.
Servlet containers maintain no affinity between threads and sessions.
This is a broken approach for storing related items in a scoped container:
(The code assumes there is already a high score in the session.)
|
|
This is not thread safe. Two different users may try to get and set the high score at the same time, with unpredictable results: the visibility problem.
One improvement - the “set-after-write” technique:
|
|
One of the motivations for this technique is to hint to the container that the value has been changed, so that the session or application state can be resynchronized across instances in a distributed Web application.
It is enough to mitigate visibility problems (i.e. that another player might never see the values updated in updateHighScore()
), but it is not enough to address the multiple potential atomicity problems.
Question: Why don’t we just do this:
|
|
Maybe there are other attributes in PlayerScore
that we don’t want to change.
The next step is to make HighScore
immutable:
|
|
The final step - use an AtomicReference
:
|
|
Even with a session-scoped container, HttpSession
, it is possible for multiple simultaneous requests to happen on the same session (especially with asynchronous calls via ajax, etc).
Tomcat’s implementation of ServletContext
is org.apache.catalina.core.ApplicationContext
.
It uses ConcurrentHashMap
for its attributes store.
The source code is here.
Regarding ConcurrentHashMap
:
“Retrieval operations (including get) generally do not block, so may overlap with update
operations (including put and remove). Retrievals reflect the results of the most recently
completed update operations holding upon their onset. (More formally, an update operation
for a given key bears a happens-before relation with any (non-null) retrieval for that key
reporting the updated value.)”
In Jetty, it appears to also be a ConcurrentHashMap
, but wrapped in AtomicReference
see here
Perhaps because their use of ConcurrentHashMap
predated the addition of atomic operations such as computeIfAbsent()
…?
ConcurrentHashMap
- since Java 5
putIfAbsent()
- since Java 5
computeIfAbsent()
, compute()
, merge()
- since Java 8
See this SO post:
If a thread puts a value in concurrent hash map then some other thread that retrieves the value for the map is guaranteed to see the values inserted by the previous thread.
This issue has been clarified in “Java Concurrency in Practice” by Joshua Bloch.
Quoting from the text :-
The thread-safe library collections offer the following safe publication guarantees, even if the javadoc is less than clear on the subject:
Placing a key or value in a Hashtable, synchronizedMap or Concurrent-Map safely publishes it to any other thread that retrieves it from the Map (whether directly or via an iterator)…
SO comment:
ConcurrentHashMap.get()
is guaranteed to see any previousput()
, where “previous” means there is a happens-before relationship between the two as specified by the Java Memory Model. (For theConcurrentHashMap
in particular, this is essentially what you’d expect, with the caveat that you may not be able to tell which happens first if both threads execute at “exactly the same time” on different cores.
Useful article: Understanding the Java Memory Model
Also worth a closer look: Java Memory Model FAQ