What is Java FencedLock?

In computer science, a lock is a synchronization mechanism that only allows a single thread access to a shared resource simultaneously. This prevents multiple threads from accessing the same resource simultaneously, which can cause race conditions and other issues and errors.

The Java programming language helps software developers perform locking via concepts such as the FencedLock. The FencedLock data structure is a distributed lock that implements the java.util.concurrent.locks.Lock interface with well-defined behavior during execution and failure.

Distributed locking is even more complicated than standard locking, which is typically concerned only with threads on a single machine. In distributed locking, different clients on separate machines may attempt to access the same resource, such as a file or database. Distributed systems need to handle problems such as partial failure, in which some system components are down while others continue operating.

The FencedLock works by using a fencing token: a number that is increased each time a client acquires the lock. The client needs to pass this token to any external services and include the token in every request. If a service has received requests with two different tokens, it accepts the request with the highest token number and rejects all other requests. This ensures that even if two clients have the lock simultaneously, the external service can only interact with one client simultaneously.

Any critical section in the code protected by a FencedLock is guaranteed to be executed by only one thread in the cluster. However, the FencedLock does not provide fairness. I.e., it cannot ensure that different threads or processes can equitably share access to the resource. The FencedLock provides consistency and partition tolerance but does not provide availability, which makes it a CP concept according to the CAP theorem.

Redis-based distributed Java FencedLock with Redisson

The FencedLock is a tremendously valuable asset for Java developers. However, it is not available out of the box for tools such as Redis. Redis is an open-source in-memory data structure store widely used as a database, cache, and message broker.

Thanks to its ability to process requests in real time, Redis is a popular choice for web applications that need fast data storage and retrieval. Redis is an excellent choice for any use case that requires storing and retrieving big data in memory efficiently.

One problem for Java programmers is that Redis doesn’t automatically support using the Java programming language. Instead, Java developers can use third-party Java frameworks like Redisson in their Redis projects.

Redisson is a Java client for Redis that includes dozens of distributed Java objects and services. By providing these familiar Java constructs, Redisson significantly lowers the Redis learning curve for developers who already know the standard Java classes and interfaces.

In particular, Redisson implements FencedLock for Redis via the RFencedLock interface. Like Hazelcast’s FencedLock, RFencedLock extends the java.util.concurrent.locks.Lock interface.

The best way to learn is by example, so below is a demonstration of how to use RFencedLock with Java and Redis:

RFencedLock lock = redisson.getFencedLock("myLock");

// traditional lock method
Long token = lock.lockAndGetToken();

// or acquire lock and automatically unlock it after 10 seconds
token = lock.lockAndGetToken(10, TimeUnit.SECONDS);

// or wait for lock aquisition up to 100 seconds 
// and automatically unlock it after 10 seconds
Long token = lock.tryLockAndGetToken(100, 10, TimeUnit.SECONDS);
if (token != null) {
   try {
     // check if token >= old token
     ...
   } finally {
       lock.unlock();
   }
}

As discussed, the RFencedLock uses a token variable that stores the number of times the lock has been acquired. The example also demonstrates how Redis Java developers can specify a given time limit on the RFencedLock. The lock will be automatically released in the code above after 10 seconds. The tryLockAndGetToken function allows the client to wait to acquire the lock for 100 seconds.

Because it extends the Java Lock interface, the RFencedLock behaves similarly to standard locks in Java. For example, only the lock owner can unlock it, or the program will throw an IllegalMonitorStateException. If you want to allow multiple clients access to the resource simultaneously (while placing an upper limit on concurrent access), consider an alternative such as the RSemaphore.

The RFencedLock is also compatible with the asynchronous, reactive, and RxJava2 programming models. The latter two use the interfaces RFencedLockReactive and RFencedLockRx, respectively. For example, the code snippet below illustrates how to use the RFencedLock with the asynchronous programming model:

RFencedLock lock = redisson.getFencedLock("myLock");

RFuture<Long> lockFuture = lock.lockAndGetTokenAsync();

// or acquire the lock and automatically unlock it after 10 seconds
RFuture<Long> lockFuture = lock.lockAndGetTokenAsync(10, TimeUnit.SECONDS);

// or wait for lock acquisition up to 100 seconds 
// and automatically unlock it after 10 seconds
RFuture<Long> lockFuture = lock.tryLockAndGetTokenAsync(100, 10, TimeUnit.SECONDS);

long threadId = Thread.currentThread().getId();
lockFuture.whenComplete((token, exception) -> {
    if (token != null) {
        try {
          // check if token >= old token
          ...
        } finally {
          lock.unlockAsync(threadId);
        }
    }
});
Similar terms