preface

The default locking logic is unfair.

When the lock fails, the thread enters the while loop and keeps trying to get the lock, which is when multiple threads compete. That means whoever grabs it will own it.

Redisson provides a fair locking mechanism that can be used as follows:

RLock fairLock = redisson.getFairLock("anyLock"); // The most common use method is fairLock. Lock ();

Let’s take a look at how fair locking is implemented.

Fair lock

Go directly to the source method: RedissonFairlock # TryLockinnerAsync.

Boy, I can’t take a screenshot of this chunk of code, so let’s go straight to the Lua script.

I don’t know anything about Lua, but if else, I can make sense of it.

Because Debug finds command == redisCommands.eval_long, look directly at the following section.

That’s a long time, several times!

So what are the parameters?

  1. Keys [1] : name of lock,anyLock;
  2. Keys [2] : Locked wait queue,redisson_lock_queue:{anyLock};
  3. Keys [3] : Set of thread locking time in wait queueredisson_lock_timeout:{anyLock}Is stored in the collection according to the timestamp of the lock;
  4. ARGV[1] : lock timeout time 30000;
  5. ARGV[2] : UUID: threaId combinationa3da2c83-b084-425c-a70f-5d9a08b37f31:1;
  6. ARGV[3] : ThreadWaitTime defaults to 300000;
  7. ARGV[4] : CurrentTime Current timestamp

Locked queues and collections are strings with curly braces. {XXXX} means that the key only uses XXXX to calculate slot position.

Lua script analysis

The above Lua script is divided into several pieces, so let’s take a look at the execution of the above code from different perspectives.

Initial lock (Thread1)

In the first part, because it is the first time to lock, the waiting queue is empty and the loop is broken. That’s the end of the execution.

Part 2:

  1. When the lock does not exist, the waiting queue is empty, or the first queue is the current thread, and both conditions are met, the internal logic is entered.
  2. Deletes the current thread from the wait queue and timeout set, which are both empty and do not require any action.
  3. Reduces the timeout of all waiting threads in the queue, and does not require any operation.
  4. Lock and set the timeout.

So this is the return. So I’m going to skip the rest of it for now.

The equivalent of the following two commands (the entire Lua script is atomic!) :

> hset anyLock a3da2c83-b084-425c-a70f-5d9a08b37f31:1 1
> pexpire anyLock 30000

Thread2 lock

When Thread1 has finished locking, Thread2 will do the locking.

Thread2 can be another thread of the instance or a thread of another instance.

In the first part, although the lock is occupied by Thread1, the wait queue is empty and the loop is broken.

The second part, the lock exists, so skip it.

The third part, whether the thread holds a lock, does not hold a lock, skip directly.

Thread2 is not in the queue until the thread is locked. Thread2 is not in the queue until the thread is locked.

Thread2 will end up here:

  1. Waiting on the queue from the threadredisson_lock_queue:{anyLock}Gets the last thread in the
  2. Because the wait queue is empty, the remaining time of the current lock is obtained directlyttl anyLock;
  3. Assemble timeout timeout = TTL + 300000 + current timestamp, 300000 is the default60000 * 5;
  4. Use zadd to put Thread2 into the ordered collection of waiting threads, and then use rpush to put Thread2 back into the wait queue.

zadd KEYS[3] timeout ARGV[2]

Here, using the zadd command, redisson_lock_timeout:{anyLock}, timeout stamp (1624612689520), thread (UUID2:Thread2).

The timeout stamp is used as a score to sort in the ordered set, indicating the order of locking.

Thread3 lock

Thread1 holds the lock and Thread2 waits while Thread3 arrives.

Get firstThreadd2 and the queue is threaded UUID2:Thread2.

Determine if the score (timeout stamp) of firstThreadd2 is smaller than the current timestamp:

  1. If it’s less than or equal, it’s out of time, so remove firstThreadd2;
  2. If it is greater than, it will go into the subsequent judgment.

The second, third and fourth parts do not meet the conditions.

Thread3 will also end up here:

  1. Waiting on the queue from the threadredisson_lock_queue:{anyLock}Gets the last thread in the
  2. TTL = lastThreadId timeout — current timeout — TTL = lastThreadId timeout — current timeout
  3. Assemble timeout timeout = TTL + 300000 + current timestamp, 300000 is the default60000 * 5The timeout stamp of Thread3 is appended to the timeout of the last thread by 300000 and the current timestamp.
  4. Use zadd to put Thread3 into the ordered collection of waiting threads, and then use rpush to put Thread3 back into the wait queue.

conclusion

This paper mainly summarizes the locking logic of fair locks, which involves a lot of Redis operations. Let’s make a brief summary:

  1. Redis Hash data structure: holds the current lock, the Redis Key is the lock, the Hash field is the locked thread, and the Hash value is the reentrant times.
  2. Redis List data structure: acts as a waiting queue for threads, and new waiting threads are placed on the right side of the queue using the rpush command.
  3. Data structure: The order in which the waiting threads are sorted. The score is used as a timeout stamp for the waiting threads.

The thing to understand is that there’s an extra wait queue, and there’s an ordered collection.

It is better to read against the Java Fair Lock source code.

Related to recommend

  • Redisson distributed lock source code 04: Reentrant lock release
  • Redisson distributed lock source code 03: Reentrant lock mutex
  • Redisson distributed lock source 02: watchdog