This is the ninth day of my participation in the First Challenge 2022. For details: First Challenge 2022.

What is a non-reentrant lock?

That is, if the current thread has already acquired the lock by executing a method, it will block when it tries to acquire the lock again in the method.

What is a reentrant lock?

A reentrant lock, also known as a recursive lock, means that an inner recursive function can still acquire the lock after an outer function acquires the lock in the same thread. When the same thread enters the same code again, it can get the lock again.

Reentrant locking?

Prevents deadlocks from occurring when locks are acquired more than once in the same thread.

Note: Synchronized and ReentrantLock are both reentrant locks in Java programming.

Synchronized based reentrant locking

Step 1: Double locking logic

public class SynchronizedDemo {
    // Simulate inventory 100
    int count=100;
    public synchronized void operation(a){
        log.info("Tier 1 lock: Destock");
        // simulate inventory reduction
        count--;
        add();
        log.info("Place order end inventory remaining :{}",count);
    }

    private synchronized void add(a){
        log.info("Layer 2 lock: Insert order");
        try {
            Thread.sleep(1000*10);
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

Step 2: Add a test class

public static void main(String[] args) {
    SynchronizedDemo synchronizedDemo=new SynchronizedDemo();
    for (int i = 0; i < 3; i++) {
        int finalI = i;
        new Thread(()->{
            log.info("------- user {} start placing orders --------", finalI); synchronizedDemo.operation(); }).start(); }}Copy the code

Step 3: Test

20:44:04. 013 / Thread - 2 INFO. Com agan. Redis. Controller. SynchronizedController -- -- -- -- -- -- -- -- the user 2 start order -- -- -- -- -- -- -- -- 20:44:04. 013 [Thread 1] INFO com. Agan. Redis. Controller. SynchronizedController -- -- -- -- -- -- -- -- the user 1 began to place an order. -- -- -- -- -- -- -- -- 20:44:04 013 [Thread - 0] INFO Com. Agan. Redis. Controller. SynchronizedController -- -- -- -- -- -- -- -- the user zero order -- -- -- -- -- -- -- -- 20:44:04. 016 / Thread - 2 INFO Com. Agan. Redis. Reentrant. SynchronizedDemo - the first layer lock: inventory reduction 20:44:04. 016 / Thread - 2 INFO Com. Agan. Redis. Reentrant. SynchronizedDemo - the second lock: [Thread insert orders 20:44:14. 017-2) INFO. Com agan. Redis. The Reentrant. SynchronizedDemo - order end inventory remaining: 99 20:44:14 017 [Thread - 0] INFO Com. Agan. Redis. Reentrant. SynchronizedDemo - the first layer lock: inventory reduction 20:44:14, 017 [Thread - 0] INFO Com. Agan. Redis. Reentrant. SynchronizedDemo - the second lock: [Thread insert orders 20:44:24. 017-0] INFO. Com agan. Redis. The Reentrant. SynchronizedDemo - order end inventory remaining: 98 20:44:24. 017 INFO [Thread - 1] Com. Agan. Redis. Reentrant. SynchronizedDemo - the first layer lock: inventory reduction 20:44:24. 017 INFO [Thread - 1] Com. Agan. Redis. Reentrant. SynchronizedDemo - the second lock: [Thread insert orders 20:44:34. 017-1) INFO. Com agan. Redis. The Reentrant. SynchronizedDemo - order ending inventory surplus: 97Copy the code
  • Because the synchronized keyword modifies methods, all locks are instance objects: synchronizedDemo
  • It can be seen from the running result that inventory reduction and order insertion are both completed by each thread, and the lock can be released only after the completion of the two methods, and other threads can take the lock, that is, a thread can get the same lock for many times, which can be re-entrant. So synchronized is also reentrant.

Reentrantlock-based ReentrantLock

ReentrantLock, which is a reentrant and exclusive lock, is a recursive non-blocking synchronous lock. Compared with synchronized keyword, it is more flexible, more powerful, increased polling, timeout, interrupt and other advanced functions.

Step 1: Double locking logic

public class ReentrantLockDemo {

    private Lock lock =  new ReentrantLock();

    public void doSomething(int n){
        try{
            // Enter the recursion first thing: lock
            lock.lock();
            log.info("-------- recursion {} times --------",n);
            if(n<=2) {try {
                    Thread.sleep(1000*2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.doSomething(++n);
            }else{
                return; }}finally{ lock.unlock(); }}}Copy the code

Step 2: Add a test class

public static void main(String[] args) {
    ReentrantLockDemo reentrantLockDemo=new ReentrantLockDemo();
    for (int i = 0; i < 3; i++) {
        int finalI = i;
        new Thread(()->{
            log.info("------- user {} start placing orders --------", finalI);
            reentrantLockDemo.doSomething(1); }).start(); }}Copy the code

Step 3: Test

20:55:23. [533] Thread - 1 INFO. Com agan. Redis. Controller. ReentrantController -- -- -- -- -- -- -- -- the user first order -- -- -- -- -- -- -- -- 20:55:23. 533 [Thread - 2] INFO com. Agan. Redis. Controller. ReentrantController -- -- -- -- -- -- -- -- the user 2 began to place an order. -- -- -- -- -- -- -- -- 20:55:23 533 [Thread - 0] INFO Com. Agan. Redis. Controller. ReentrantController -- -- -- -- -- -- -- -- the user zero order -- -- -- -- -- -- -- -- 20:55:23. 536 INFO [Thread - 1] Com. Agan. Redis. Reentrant. ReentrantLockDemo -- -- -- -- -- -- -- -- -- recursive one. -- -- -- -- -- -- -- -- 20:55:25 537 INFO [Thread - 1] Com. Agan. Redis. Reentrant. ReentrantLockDemo -- -- -- -- -- -- -- -- -- recursive 2 times -- -- -- -- -- -- -- -- 20:55:27. 538 INFO [Thread - 1] Com. Agan. Redis. Reentrant. ReentrantLockDemo -- -- -- -- -- -- -- -- -- recursive three times -- -- -- -- -- -- -- -- 20:55:27. 538 / Thread - 2 INFO Com. Agan. Redis. Reentrant. ReentrantLockDemo -- -- -- -- -- -- -- -- -- recursive one. -- -- -- -- -- -- -- -- 20:55:29 538 [Thread - 2] INFO Com. Agan. Redis. Reentrant. ReentrantLockDemo -- -- -- -- -- -- -- -- -- recursive 2 times -- -- -- -- -- -- -- -- 20:55:31. 539 / Thread - 2 INFO Com. Agan. Redis. Reentrant. ReentrantLockDemo -- -- -- -- -- -- -- -- -- recursive 3 times. -- -- -- -- -- -- -- -- 20:55:31 539 [Thread - 0] INFO Com. Agan. Redis. Reentrant. ReentrantLockDemo -- -- -- -- -- -- -- -- -- recursive one. -- -- -- -- -- -- -- -- 20:55:33 539 [Thread - 0] INFO Com. Agan. Redis. Reentrant. ReentrantLockDemo -- -- -- -- -- -- -- -- -- recursive 2 times. -- -- -- -- -- -- -- -- 20:55:35 540 [Thread - 0] INFO Com. Agan. Redis. Reentrant. ReentrantLockDemo -- -- -- -- -- -- -- -- -- recursive three times -- -- -- -- -- -- -- --Copy the code
  • ReentrantLock is ReentrantLock. ReentrantLock is ReentrantLock.

How does Redis implement distributed reentrant locking?

Although setNX can achieve distributed lock, it cannot be reentrant. In some complex business scenarios, when we need distributed reentrant lock, there are still many solutions for redis reentrant lock industry, and the most popular one is Redisson

What is a Redisson?

  • Redisson is the official Redis recommended Java version of Redis client.
  • Based on common interfaces in Java utility toolkit, a series of common utility classes with distributed characteristics are provided for users.
  • In the network communication is based on NIO Netty framework, to ensure the high performance of network communication.
  • On the function of distributed lock, it provides a series of distributed locks. Such as:
    • Reentrant Lock
    • Fair Lock
    • UnFair Lock
    • ReadWriteLock
    • MultiLock
    • RedLock

Case combat: experience redis distributed reentry lock

Step 1:Redisson configuration

Redisson configuration can be checked: Redis distributed cache (34) – SpringBoot integration Redission – dig gold (juejin. Cn)

Step 2:Redisson reentrant lock code

public class RedisController {

    @Autowired
    RedissonClient redissonClient;

    @GetMapping(value = "/lock")
    public void get(String key) throws InterruptedException {
        this.getLock(key, 1);
    }

    private void getLock(String key, int n) throws InterruptedException {
        // Simulate recursion, exit after 3 recursions
        if (n > 3) {
            return;
        }
        // Step 1: Obtain a distributed reentrant lock RLock
        / / distributed reentrant Lock RLock: a Java implementation. Util. Concurrent. The locks. Lock interface, at the same time also supports automatic unlock date.
        RLock lock = redissonClient.getLock(key);
        // Step 2: Try to get the lock
        // 1. Lock by default
        //lock.tryLock();
        // 2. The account is unlocked automatically after 10 seconds. You do not need to invoke the UNLOCK method to unlock the account
        //lock.tryLock(10, TimeUnit.SECONDS);
        // 3. Try to lock the device. Wait 3 seconds at most
        // lock.tryLock(3, 10, TimeUnit.SECONDS);
        boolean bs = lock.tryLock(3.10, TimeUnit.SECONDS);
        if (bs) {
            try {
                // Business code
                log.info("Thread {} business logic processing: {}, recursive {}" ,Thread.currentThread().getName(), key,n);
                // Simulate processing business
                Thread.sleep(1000 * 5);
                // The simulation enters recursion
                this.getLock(key, ++n);
            } catch (Exception e) {
                log.error(e.getLocalizedMessage());
            } finally {
                // Step 3: Unlock the account
                lock.unlock();
                log.info("Thread {} unlock exit",Thread.currentThread().getName()); }}else {
            log.info("Thread {} did not acquire lock",Thread.currentThread().getName()); }}}Copy the code

RLock three locking actions:

    1. Lock by default
    • lock.tryLock();
    1. Supports the expiration unlock function. The account will be unlocked automatically after 10 seconds
    • lock.tryLock(10, TimeUnit.SECONDS);
    1. Wait 3 seconds at most. After the lock expires in 10 seconds, the account will be unlocked automatically
    • lock.tryLock(3, 10, TimeUnit.SECONDS);

The difference between:

  • Lock. lock() : blocking wait. The default lock is 30 seconds
    • Automatic renewal of the lock. If the service is too long, the lock is automatically locked for a new 30s during operation. There is no need to worry about the lock being deleted when it expires automatically due to long service time (default renewal)
    • The lock will not be renewed once the lock service is complete. Even if you do not manually unlock the lock, the lock will automatically expire within 30 seconds by default, avoiding deadlock
    • Lock () If we do not specify lock timeout, use: lockWatchdogTimeout = 30 * 1000
    • Principle: As long as the lock is successfully occupied, it will start a scheduled task [reset the lock expiration time, the new expiration time is the default watchdog time], every 10 seconds will automatically renew again, continued for 30 seconds
  • Lock. Lock (10, timeUnit.seconds) : the system automatically locks the account within 10 SECONDS. The automatic lock time must be longer than the service execution time
    • Problem: After the lock time expires, it will not be automatically renewed
    • Lock (10, timeunit.seconds) If we pass the timeout period for the lock, we send the redis script to lock it. The default timeout is the specified time

Best Combat:

  • lock.lock(10,TimeUnit.SECONDS); Do not renew the watchdog. Ensure that the automatic unlock time is longer than the service execution time. Manually unlock the watchdog

Step 3: Test

Ie stray device input: http://127.0.0.1:9090/lock? Key = LJW Refresh 3 times to see the effect

Thread - nio - HTTP9090-exec-1Business logic processing: LJW, recursion1Thread - nio - HTTP9090-exec-2No lock thread http-nio-9090-exec-1Business logic processing: LJW, recursion2Thread - nio - HTTP9090-exec-3No lock thread http-nio-9090-exec-1Business logic processing: LJW, recursion3Thread - nio - HTTP9090-exec-1Unlock exit thread http-nio-9090-exec-1Unlock exit thread http-nio-9090-exec-1Unlock the exitCopy the code

Test results:

  • The niO-9090-exec-1 thread recurses three times in the getLock method, which proves that lock.tryLock is a reentrant lock
  • Io-9090-exec-2 nio-9090-exec-3 is not locked because lock.tryLock(3, 10, timeunit. SECONDS) is not attempted. After 10 seconds after the expiration of the automatic unlock so wait for 3 seconds can not wait, gave up

conclusion

Distributed reentrant locking is described above, which proves that Redisson implements reentrant locking. The Redisson toolkit also includes ReadWriteLock and RedLock, which we’ll explore in more detail in the next article.

Write in the last

  • 👍🏻 : have harvest, praise encouragement!
  • ❤️ : Collect articles, easy to look back!
  • 💬 : Comment exchange, mutual progress!