Table of contents
Open Table of contents
Background
Our sales page contains a calendar-like control where users can find events in the current month with tickets available. Multiple users see the same content, so there is no need to query the database again for every request. To improve the user experience and reduce server load during major promotions, we decided to cache the control’s response. Originally, we used Ehcache 2’s SimplePageFragmentCachingFilter.
Because Ehcache 2 is no longer maintained, we decided to migrate to Ehcache 3. During the migration, we found that Ehcache 2’s ehcache-web was no longer supported. Our project still used its SimplePageCachingFilter, so we copied that class’s source and adapted it to Ehcache 3. In doing so, we discovered that the implementation uses Java’s read-write lock.
Ehcache 2 Source Analysis: How Web Page Caching Works
- The core page-caching logic is in net.sf.ehcache.constructs.web.filter.CachingFilter#buildPageInfo

- The blockingCache get method is shown below.

- First, obtain a lock instance of net.sf.ehcache.concurrent.ReadWriteLockSync.
This class contains a Java ReentrantReadWriteLock. - With concurrent threads, it first acquires and immediately releases the read lock, then acquires the write lock. The first successful write-lock acquisition returns a null Element, and the write lock remains held. Other concurrent threads unable to acquire it wait for a configurable interval.

- After obtaining the write lock, execution returns to step 1, calls buildPage, and finally stores the object with blockingCache.put. The write lock is released here.

- In summary, Ehcache 2 caches the response when the browser requests a URL matching the Filter’s url-pattern. The concurrent caching logic uses Java’s ReentrantReadWriteLock, which addresses the performance problem of many concurrent requests for the same page.
Analyzing ReentrantReadWriteLock
Read-Write Lock Properties
Look directly at the comments:
- Concurrent threads acquiring read locks do not exclude one another. If a thread holds the write lock, however, a reader must wait until it is released.

- A write lock can be obtained immediately if no other thread holds a read or write lock. Otherwise, the writer waits for those locks to be released.

How Is the Read-Write Lock Implemented?
As discussed in Locks in Java (Part 2): Implementing a Custom Lock, a custom lock implements Lock, overrides AQS methods, and calls the AQS template methods from the Lock implementation. Those template methods ultimately call the overridden AQS methods.
-
Its main structure follows the approach described above.

-
For example, the read lock’s lock method calls the AQS template method acquireShared, which eventually calls tryAcquireShared in ReentrantReadWriteLock’s inner Sync class.

Migrating Ehcache 2 Web Source to Ehcache 3 / Implementing High-Performance Page Caching with a Custom Filter
With the logic and lock properties above in mind, the implementation is straightforward:
- Create a custom Filter that caches web responses in Ehcache 3.
- To improve access to the same page under high concurrency, use a read-write lock.
- Each cached page URL should contain an identifier used as its cache key.
- When concurrent requests ask for the same page, look up the key in the cache. Return the cached response immediately if found.
- If not found, acquire the read lock and release it immediately without doing any work. Wait if the read lock cannot be acquired.
- Immediately compete for the write lock. Once acquired, check the cache again: this thread might not be the first writer, and an earlier writer may already have populated it. If the entry exists, release the write lock. Otherwise, write the page response to the cache, then release the write lock.