«Top»

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.

Initialization

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);
}

Shutdown

The contextDestroyed()
method is empty, as there is no need for shutting down the Map.

@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {}

Write

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 );
}

Read

@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();
}

How To Run

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.

Next

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:

Monitoring-Workshop 2017 12./13.9. Düsseldorf