preface

The default locking logic is unfair.

When the lock fails, the thread enters a while loop and keeps trying to acquire the lock, with multiple threads competing. Which means whoever gets it gets it.

Redisson provides a fair lock mechanism, which can be used as follows:

RLock fairLock = redisson.getFairLock("anyLock");
// The most common way to use it
fairLock.lock();
Copy the code

Let’s see how fair lock is implemented.

Fair lock

RedissonFairLock#tryLockInnerAsync RedissonFairLock#tryLockInnerAsync

Boy, this chunk of code, I can’t finish screenshots, let’s go straight to the Lua script.

PS: Although I don’t understand Lua, but this pile of if else we can probably understand.

Because debug finds command == rediscommands.eval_long, look directly at the following section.

Such a long time, repeatedly shout good boy!

So what are the parameters?

  1. KEYS[1] : the name of the lockanyLock;
  2. KEYS[2] : lock wait queue,redisson_lock_queue:{anyLock};
  3. KEYS[3] : set of waiting for the thread lock time in the queue,redisson_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:ThreadId combinationa3da2c83-b084-425c-a70f-5d9a08b37f31:1;
  6. ARGV[3] : threadWaitTime defaults to 300000;
  7. ARGV[4] : currentTime Indicates the current timestamp.

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

Lua script analysis

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

First lock (Thread1)

The first part, because it is the first time to lock, so the wait queue is empty, directly out of the loop. This part is done.

Part II:

  1. When the lock does not exist, the wait queue is empty or the queue head is the current thread, the internal logic is entered.
  2. Removes the current thread from the wait queue and timeout set, both empty, and no action is required.
  3. Reduce the timeout of all waiting threads in the queue, and no action is required;
  4. Lock and set timeout.

I’m done here and I return. So I’m going to skip the rest of it.

Equivalent to the following two commands (the entire Lua script is atomic!) :

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

Thread2 lock

When Thread1 is done locking, Thread2 now locks.

Thread2 can be another thread of this 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, is skipped.

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

If the thread is waiting for the lock, Thread2 will skip it.

Thread2 will end up here:

  1. Wait from thread to queueredisson_lock_queue:{anyLock}Get the last thread from
  2. Because the wait queue is empty, the remaining time of the current lock is obtained directlyttl anyLock;
  3. Assembly timeout time timeout = TTL + 300000 + current timestamp, 300000 is the default60000 * 5;
  4. Zadd is used to place Thread2 into the ordered collection of waiting threads, and Rpush is used to place Thread2 into the wait queue.

zadd KEYS[3] timeout ARGV[2]

Redisson_lock_timeout :{anyLock}, timeout timestamp (1624612689520), thread (UUID2:Thread2) using zadd command.

Where the timeout timestamp is used when the score is sorted in an ordered set, indicating the order of the lock.

Thread3 lock

Thread1 holds the lock, Thread2 waits, and thread 3 arrives.

Get firstThreadId2 where the queue is threaded and UUID2:Thread2.

Determine if the score of firstThreadId2 (timeout timestamp) is less than the current timestamp:

  1. If the value is less than or equal to a timeout, remove firstThreadId2.
  2. If the value is greater than, the subsequent judgment will be entered.

Parts two, three and four do not meet the conditions.

Thread3 will also end up here:

  1. Wait from thread to queueredisson_lock_queue:{anyLock}Get the last thread from
  2. TTL = lastThreadId TTL = lastThreadId TTL = lastThreadId TTL = lastThreadId TTL = lastThreadId
  3. Assembly timeout time timeout = TTL + 300000 + current timestamp, 300000 is the default60000 * 5Add 300,000 to the timeout of the last thread and the current timestamp to get the timeout of Thread3.
  4. Zadd is used to place Thread3 into the ordered collection of waiting threads, and Rpush is used to place Thread3 into the wait queue.

conclusion

This article mainly summarizes the lock logic of fair lock, which involves more Redis operations, make a brief summary:

  1. Redis Hash data structure: stores the current lock. The Redis Key is the lock, the Hash field is the lock thread, and the Hash value is the reentrant count.
  2. Redis List data structure: acts as a thread wait queue. New wait threads are placed to the right of the queue using rpush.
  3. Redis sorted set Ordered collection data structure: Store the order of waiting threads. Score is used as timeout stamp for waiting threads.

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

Compare Java fair lock source code to read, understand the effect is better.

Related to recommend

  • 04: Reentrant lock release
  • Reentrant locks are mutually exclusive
  • Redisson distributed lock source code 02: watchdog