ReentrantLock的实现语义与使用场景

简介

ReentrantLock(重入锁)就是支持可重进入的锁,它表示该锁能支持一个线程对资源的重复加锁。另外还支持获取锁的公平和非公平选择ReentrantLock的实现不仅可以替代隐式的synchronized关键字,而且还能够提供超过关键字本身的多种功能。

公平与非公平

这个概念是针对锁的获取的,在绝对时间上,先对锁进行获取的请求一定先满足,那么这个锁是公平的,反之就是不公平的。公平锁的获取就是等待时间最长的线程最先获取锁,也就是锁获取是顺序的。但是公平锁的机制往往效率不高。

ReentrantLock的调用过程

ReentrantLock把所有Lock接口的操作都委派到一个sync类上,该类继承了队列同步器:

static abstract class Sync extends AbstractQueuedSynchronizer {
final static class NonfairSync extends Sync
final static class FairSync extends Sync

1、sync有两个子类,分别实现的是公平锁和非公平锁,默认为非公平锁。

2、由于该同步组件的实现使用的模板方法模式,我们看下ReentrantLock.lock()方法的调用过程(默认为非公平锁)

非公平锁语义

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

1、首先判断锁当前的状态,如果锁空闲,则当前线程尝试获取。

2、当前线程获取锁成功,则设置当前线程为锁的拥有者。

3、如果锁状态为非空闲状态且当前线程为锁的拥有者,则直接将同步状态值进行增加并返回true,表示获取同步状态成功。

这里就是支持锁重入的场景,跟synchronized类似:成功获取锁的线程再次获取锁,只是增加了同步状态值,与此对应的是,在释放锁的时候需要对应减少同步状态值,直到同步状态值为0的时候,才表示这把锁真正的被释放了。

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

公平锁语义

 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (isFirst(current) &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

这里与非公平锁大部分地方类似,只是线程在获取同步状态时,需要判断当前节点的前驱节点是否为首节点。公平锁总是按照队列的顺序来来获取锁的。

ReentrantLock的使用场景

1、发现该操作已经在执行中则不再执行

a、用在定时任务时,如果任务执行时间可能超过下次计划执行时间,确保该任务只有一个正在执行,忽略重复触发。

b、用在界面交互时点击执行较长时间请求操作时,防止多次点击导致后台重复执行。

主要用于进行非重要任务时防止重复执行。

private ReentrantLock lock = new ReentrantLock();
    
    public void test()
    {
        if(lock.tryLock())
        {
            try
            {
               //dosomothing 
            }
           finally
           {
               lock.unlock();
           }
        }
    }

2、如果该操作已经在执行,则等待一个个执行(同步执行,与synchronized类似)

3、如果该操作已经在执行,则尝试等待一段时间,超时则放弃执行

 if(lock.tryLock(5, TimeUnit.SECONDS))
        {
            try
            {
               //dosomothing 
            }
           finally
           {
               lock.unlock();
           }
        }

这种其实属于场景2的改进,等待获得锁的操作有一个时间的限制,如果超时则放弃执行。
用来防止由于资源处理不当长时间占用导致死锁情况(大家都在等待资源,导致线程队列溢出)。

场景4:如果发现该操作已经在执行,等待执行。这时可中断正在进行的操作立刻释放锁继续下一操作。

synchronized与Lock在默认情况下是不会响应中断(interrupt)操作,会继续执行完。lockInterruptibly()提供了可中断锁来解决此问题。(场景2的另一种改进,没有超时,只能等待中断或执行完毕)

原文地址:https://www.cnblogs.com/dongguacai/p/5992838.html