Redis distributed lock (C): Supports reentrant lock, to avoid deadlock when the lock recursively

Use the status quo

Redis basis of content distributed lock, we have the Redis Distributed Lock: simple version based Distributed Lock AOP and Redis realization of this article mentioned, the article also demonstrates the normal lock and unlock methods.

Why Redis distributed lock to support the renewal, and how to support renewal, we also have the Redis Distributed Lock (B): After renewed support for the lock, the lock timeout to avoid multiple threads lead to acquire the lock

Within six months from the last time after upgrading to the renewable distributed lock, this self-study version of the simple distributed lock is still running well.

problem found

But accidentally discovered in a recent online search log when there is a business case scenario, distributed lock deadlock ensues.

We passed a preliminary investigation, locate the call because another method of execution need to lock in a method already holds the lock in the lead.

The simplified function as shown below:

@Service
public class Lock1ServiceImpl implements Lock1Service {
    @Override
    @LockAnnotation(lockField = "test", lockKey = "1")
    public void test() {
    
    }
}

@Service
public class Lock2ServiceImpl implements Lock2Service {

    @Autowired
    private Lock1Service lock1Service;

    @Override
    @LockAnnotation(lockField = "test", lockKey = "1")
    public void test(){
        lock1Service.test();
    }
}

复制代码

Deadlock process mainly as follows:

Lock2Service of test methods need to lock (lock key as "test: 1"), then the method call inside the Lock1Service the test method, the test method and Lock1Service also need to add the same lock.

In the implementation of the lock1Service.test (), Lock2Service the test method does not perform the end, it will not release the lock, and lock1Service perform test methods and have to go to get a lock, this time a deadlock occurs.

This code can only wait lock1Service after a period of time to wait to take the initiative to give up to continue after the lock in order to proceed down. The test method lock1Service will never be executed.

solution

Now that the problem has occurred, then the next we have to do is to find ways to avoid this situation.

We soon expect the jdk that comes with ReentrantLock, we can achieve reentrant distributed lock in accordance with the same principle.

Realization of the principle reentrant and also ReentrantLock principle is similar to and different from that ReentrantLock distributed lock in for the first time to acquire the lock when the need redis distributed to compete. And when already holds the lock and require re-entry, distributed lock will be downgraded to the local lock. Only the same thread resource concepts have reentrant.

The following are details of the main lock type, locking and unlocking process shown as follows:

final Boolean tryLock(String lockValue, int waitTime) {
    long startTime = System.currentTimeMillis();
    long endTime = startTime + waitTime * 1000;
    try {
        do {
            final Thread current = Thread.currentThread();
            int c = this.getState();
            if (c == 0) {
                int lockTime = LOCK_TIME;
                if (lockRedisClient.setLock(lockKey, lockValue, lockTime)) {
                    lockOwnerThread = current;
                    this.setState(c + 1);
                    survivalClamProcessor = new SurvivalClamProcessor(lockKey, lockValue, lockTime, lockRedisClient);
                    (survivalThread = threadFactoryManager.getThreadFactory().newThread(survivalClamProcessor)).start();
                    log.info("线程获取重入锁成功,锁的名称为{}", lockKey);
                    return Boolean.TRUE;
                }
            } else if (lockOwnerThread == Thread.currentThread()) {
                if (c + 1 < 0) {
                    throw new Error("Maximum lock count exceeded");
                }
                this.setState(c + 1);
                log.info("线程重入锁成功,锁的名称为{},当前LockCount为{}", lockKey, state);
                return Boolean.TRUE;
            }
            int sleepTime = SLEEP_TIME_ONCE;
            if (waitTime > 0) {
                log.info("线程暂时无法获得锁,当前已等待{}ms,本次将再等待{}ms,锁的名称为{}", System.currentTimeMillis() - startTime, sleepTime, lockKey);
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    log.info("线程等待过程中被中断,锁的名称为{}", lockKey, e);
                }
            }
        } while (System.currentTimeMillis() <= endTime);
        if (waitTime == 0) {
            log.info("线程获得锁失败,将放弃获取锁,锁的名称为{}", lockKey);
        } else {
            log.info("线程获得锁失败,之前共等待{}ms,将放弃等待获取锁,锁的名称为{}", System.currentTimeMillis() - startTime, lockKey);
        }
        return Boolean.FALSE;
    } catch (Exception e) {
        return Boolean.FALSE;
    }
}
复制代码
final void unLock(String lockValue) {
    if (lockOwnerThread == Thread.currentThread()) {
        int c = this.getState() - 1;
        if (c == 0) {
            this.setLockOwnerThread(null);
            survivalClamProcessor.stop();
            survivalThread.interrupt();
            this.setSurvivalClamProcessor(null);
            this.setSurvivalThread(null);
            lockRedisClient.delLock(lockKey, lockValue);
            log.info("重入锁LockCount-1,线程已成功释放锁,锁的名称为{}", lockKey);
        } else {
            log.info("重入锁LockCount-1,锁的名称为{},剩余LockCount为{}", lockKey, state);
        }
        this.setState(c);
    }
}

复制代码

Then in order to prevent users often forget to unlock or unlock not standardized, so the method is not locking and unlocking of external exposure, exposed only outside the execute method:

public <T> T execute(Supplier<T> supplier, int waitTime) {
    String randomValue = UUID.randomUUID().toString();
    Boolean holdLock = Boolean.FALSE;
    try {
        if (holdLock = this.tryLock(randomValue, waitTime)) {
            return supplier.get();
        }
        return null;
    } catch (Exception e) {
        log.error("execute error", e);
        return null;
    } finally {
        if (holdLock) {
            this.unLock(randomValue);
        }
    }
}
复制代码

Also in the aop achieved, because they can not obtain from the context objects to lock the same, it needs to acquire the lock object via lockManager.getLock (lockField, lockKey). If lockPrefix lockKey and consistent, then, will be given the same lock object is obtained, in order to achieve the reentrant feature.

public Lock getLock(String lockPrefix, String lockKey) {
    String finalLockKey = StringUtils.isEmpty(lockPrefix) ? lockKey : (lockPrefix.concat(":").concat(lockKey));
    if (lockMap.containsKey(finalLockKey)) {
        return lockMap.get(finalLockKey);
    } else {
        Lock lock = new Lock(finalLockKey, lockRedisClient, threadFactoryManager);
        Lock existLock = lockMap.putIfAbsent(finalLockKey, lock);
        return Objects.nonNull(existLock) ? existLock : lock;
    }
}
复制代码

##Release Notes

This update, in addition to achieving a distributed lock than the reentrant function, also being distributed lock @LockAnnotation declarative annotation based on the realization of the programmatic distributed lock achieved by the execute method.

In addition, since the previous version has been achieved renewable function, so lockTime mark on the LockAnnotation as expired, lock expiration time unified into 30s. It takes a long time to achieve lock function by function renewal.

Version ## follow-up program is currently implemented, it has been the scene can meet most distributed lock (equity + non-automatic renewal + reentrant distributed lock), it may have been put into production environments. But still support fair locks, and the use of spin mode + waiting threads are threaded lock blocking when competition fails to follow-up may be to optimize both directions.

Well, we next goodbye, welcome to discuss the message. Also welcomed the thumbs are welcome to send a small star ~

Guess you like

Origin juejin.im/post/5d462a9c6fb9a06ad5470172