Inspired by this Brian Goetz article: Are all stateful Web applications broken?
The Servlets API
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.
ServletContext object is contained within the
ServletConfig object, which the Web server provides to the servlet when the servlet is initialized.
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.
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).
HttpRequest objects in the Servlet specification are referred to as scoped containers.
Lifetime of scoped containers:
|lifetime of the request|
|lifetime of a session between a user and the application|
|lifetime of the application|
For the last 2,
setAttribute() methods may be called at any time by different threads.
When an HTTP request arrives at the servlet container,
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.
How Not to Store Data in a Scope
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.
Set After Write
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
The final step - use an
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
ConcurrentHashMap for its attributes store.
The source code is here.
“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
ConcurrentHashMap - since Java 5
putIfAbsent() - since Java 5
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)…
ConcurrentHashMap.get()is guaranteed to see any previous
put(), where “previous” means there is a happens-before relationship between the two as specified by the Java Memory Model. (For the
ConcurrentHashMapin 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