At the moment there is no standard concurrency control mechanism for CDI managed beans, and at the time of writing it has not been taken into consideration for CDI 2.0 specification either.

The lack of any container based concurrency control, can lead to issues when working with unprotected contexts such as Application- or Session-Scoped beans.

Unlike EJB’s, where the container handles concurrency control, CDI only defines concurrency control for the conversation context.

The container ensures that a long-running conversation may be associated with at most one request at a time, by blocking or rejecting concurrent requests. If the container rejects a request, it must associate the request with a new transient conversation and throw an exception of type javax.enterprise.context.BusyConversationException from the restore view phase of the JSF lifecycle. The application may handle this exception using the JSF ExceptionHandler.

Conversation context lifecycle in Java EE – CDI Spec

Aside from this concurrency is only mentioned in conjunction with CDI EJB interaction.

Whilst all other relevant normal scoped contexts (Application- and Session- Scoped) are completely unguarded, the specification only defines a non adaptable concurrency control for conversation scoped beans. In my previous post on sychronizing access to CDI Conversations, I described how some of these limitation can be overcome.



As mentioned, the CDI specification (see CDI Spec. 1.2 – 1.2.2 Relationship to EJB) states the possibility of employing EJB session beans for concurrency control.

@SessionScoped @Stateful public class MySingleton { public void threadUnsafeOperation() { ... } public void threadSafeOperation { ... } }

CDI manages the context and lifecycle of the stateful session bean, utilizing the EJB containers standard concurrency mechanics. The main limitation of this approach is that, apart from EJB Singleton (see EJB 3.1-Spec, Chap. 4.8.5 Singleton Concurrency), EJB concurrency control defaults to serializing all incoming requests.

By default, clients are allowed to make concurrent calls to a stateful session object and the container is required to serialize such concurrent requests. Note that the container never permits multi-threaded

access to the actual stateful session bean instance. For this reason, Read/Write method locking metadata, as well as the bean-managed concurrency mode, are not applicable to stateful session beans and must not be used. See Section 4.8.5 for a description of how these mode/locking types apply to Singleton session beans.

EJB 3.1 Spec – 4.3.14 Serializing Session Bean Methods

In this post we will explore how CDI interceptors can be used to implement EJB @Singleton style locking for CDI beans.

CDI interceptors are based upon the Interceptors Specification which was part of JSR-318 Enterprise JavaBeans.

The lifecycle of an interceptor instance is the same as that of the target class instance with which it is associated.

2.2 Interceptor Life Cycle – JSR 318 Interceptors 1.2

A locking interceptor binding has to be defined, which will be applicable to both type and method.

@Inherited @InterceptorBinding @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.TYPE}) public @interface Lock { LockType value() default LockType.WRITE; }

Since the interceptors are directly bound to the bean and its lifecycle, a lock can be defined in the interceptor for the bean.

@Lock @Interceptor public class LockInterceptor { private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); @AroundInvoke public Object concurrencyControl(InvocationContext ctx) throws Exception { Lock lockAnnotation = ctx.getMethod().getAnnotation(Lock.class); if (lockAnnotation == null) { lockAnnotation = ctx.getTarget().getClass().getAnnotation(Lock.class); } Object returnValue = null; switch (lockAnnotation.value()) { case WRITE: ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); try { writeLock.lock(); returnValue = ctx.proceed(); } finally { writeLock.unlock(); } break; case READ: ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); try { readLock.lock(); returnValue = ctx.proceed(); } finally { readLock.unlock(); } break; } return returnValue; } }

It is important to note that by default ReentrantReadWriteLock does not guarantee the order in which waiting threads are granted access and is prone to thread starvation. This can be overriden, at the cost of throughput, by providing the constructor with a “fairness” parameter.

Non-fair mode (default)

When constructed as non-fair (the default), the order of entry to the read and write lock is unspecified, subject to reentrancy constraints. A nonfair lock that is continuously contended may indefinitely postpone one or more reader or writer threads, but will normally have higher throughput than a fair lock.

Fair mode

When constructed as fair, threads contend for entry using an approximately arrival-order policy. When the currently held lock is released, either the longest-waiting single writer thread will be assigned the write lock, or if there is a group of reader threads waiting longer than all waiting writer threads, that group will be assigned the read lock.

ReentrantReadWriteLock – Java 8

The interceptor can be applied globally by declaring it on the bean:

@Lock @ApplicationScoped public class MyAppScopedBean { @Lock(LockType.READ) public int threadSafeOperation() { ... } public void threadUnsafeOperation() { ... } }

In which case the lock type can be overridden by defining it on the method.

Or placing the interceptor only on methods that need concurrency control:

@ApplicationScoped public class MyAppScopedBean { public int threadSafeOperation() { ... } @Lock public void threadUnsafeOperation() { ... } }

In which case the interceptor does not have to be defined on thread-safe methods, since it will be invoked as per normal without any concurrency control from the container.

It is important to note that this will not work for conversation context. As described above, the specification mandates that the container handles concurrency control for long-running conversations. This therefore renders any attempt to control concurrent access via interceptors futile, since it is preempted by the containers.