This page introduces the
ConcurrentMap-based
reference implementation of our example application.
As we will show in the JSR107 introduction in part 02,
Java Caches are based on
java.util.concurrent.ConcurrentMap
.
In order to introduce the example application, we present a
ConcurrentMap-based implementation here, and point out the shortcomings
from not using caches.
We will refer to this implementation in the following parts
when we point out the differences between a cache and a ConcurrentMap.
ConcurrentMap allows the implementation to be thread safe without using
synchronized blocks. This is the main feature that makes ConcurrentMap
suitable as the basic type for distributed caches: While synchronization works
well in a single VM, it cannot be used across multiple VMs.
As we will see in part 03 and
part 04, ConcurrentMap’s atomic
operations can be implemented in a distributed way scaling across multiple VMs.
The ConcurrentMap-based implementation shown here is obviously incomplete:
As there is no housekeeping, the heap will grow with each user. This will
eventually result in an OutOfMemoryError.
The ServletContextListener
initializes the map and stores it in the Servlet context:
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext context = servletContextEvent.getServletContext();
context.setAttribute(CACHE, new ConcurrentHashMap<String, UserEventList>());
}
The RestInterface reads the map from the Servlet context:
//...
@Context
private ServletContext context;
private ConcurrentMap<String, UserEventList> map;
@PostConstruct
@SuppressWarnings("unchecked")
public void init() {
map = (ConcurrentMap) context.getAttribute(CACHE);
}
The contextDestroyed()
method is empty, as there is no need for shutting down the Map.
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {}
The RestInterface provides the Web services for writing and searching
events:
@POST
@Path("{user}")
@Consumes(MediaType.APPLICATION_JSON)
public void appendEvent(@PathParam("user") String user, String msg) {
boolean success;
map.putIfAbsent(user, UserEventList.emptyList());
do {
UserEventList oldMsgList = map.get(user);
UserEventList newMsgList = UserEventList.append(oldMsgList, msg);
success = map.replace(user, oldMsgList, newMsgList);
}
while ( ! success );
}
@GET
@Path("{user}")
@Produces(MediaType.APPLICATION_JSON)
public List<String> searchByUser(@PathParam("user") String user) {
UserEventList result = map.get(user);
if ( result == null ) {
return new ArrayList<>();
}
return result.getMessages();
}
Our example code is hosted on GitHub.
The Map-based implementation can be run with maven:
mvn tomcat7:run-war -pl part01 -am verify
The Web interface is then accessible via
http://localhost:8080.
This is the last page of our introduction of the example application.
In the next part, we will show how the ConcurrentMap can be replaced
with a local cache: