13518219792

建站动态

根据您的个性需求进行定制 先人一步 抢占小程序红利时代

被问到ReentrantLock,你真的能答好吗?

一、先了解一下

我们知道实现一把锁要有如下几个逻辑:

我们在讲解AQS的时候说过AQS基本负责了实现锁的全部逻辑,唯独线程抢锁和线程释放锁的逻辑是交给子类来实现了,而ReentrantLock作为最常用的独占锁,其内部就是包含了AQS的子类实现了线程抢锁和释放锁的逻辑。

我们在使用ReentrantLock的时候一般只会使用如下方法:

ReentrantLock lock=new ReentrantLock();
 lock.lock();
 lock.unlock();
 lock.tryLock();
 Condition condition=lock.newCondition();
 condition.await();
 condition.signal();
 condition.signalAll();

二、技术架构

如果我们自己来实现一个锁,那么如何设计呢?

根据AQS的逻辑,我们写一个子类sync,这个类一定会调用父类的acquire方法进行上锁,同时重写tryAcquire方法实现自己抢锁逻辑,也一定会调用release方法进行解锁,同时重写tryRelease方法实现释放锁逻辑。

那么ReentrantLock是怎么实现的呢?

ReentrantLock的实现的类架构如下,ReentrantLock对外提供作为一把锁应该具备的api,比如lock加锁,unlock解锁等等,而它内部真正的实现是通过静态内部类sync实现,sync是AQS的子类,是真正的锁,因为这把锁需要支持公平和非公平的特性,所以sync又有两个子类FairSync和NonfairSync分别实现公平锁和非公平锁。

因为是否公平说的是抢锁的时候是否公平,那两个子类就要在上锁方法acquire的调用和抢锁方法tryAcquire的重写上做文章。

公平锁做了什么文章?

static final class FairSync extends Sync {
  
        final void lock() {
            acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    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;
        }
    }

公平锁比较简单,直接调用了父级类AQS的acquire方法,因为AQS的锁默认就是公平的排队策略。

重写tryAcquire方法的逻辑为:

公平就公平在老老实实排队。

非公平锁做了什么文章?

static final class NonfairSync extends Sync {
      
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
    //nonfairTryAcquire代码在父类sync里面
     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;
        }

非公平锁也很简单,没有直接调用了父级类AQS的acquire方法,而是先通过cas抢锁,它不管等待队列中有没有其他线程在排队,直接抢锁,这就体现了不公平。

它重写tryAcquire方法的逻辑为:

公平锁和非公平分别重写了tryAcquire方法,来满足公平和非公平的特性。那么tryAcquire方法也是需要子类重写的,因为它和是否公平无关,因此tryAcquire方法被抽象到sync类中重写。

sync类中
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;
        }

释放锁的逻辑如下:

释放锁往往和抢锁逻辑是对应的,每个子类抢锁逻辑不同的话,释放锁的逻辑也会对应不同。

三、具体实现

接下来我们通过ReentrantLock的使用看下它的源码实现:

class X {
            private final ReentrantLock lock = new ReentrantLock();
            Condition condition1=lock.newCondition();
            Condition condition2=lock.newCondition();
            public void m() {
                lock.lock();
                try {

                    if(条件1){
                        condition1.await();
                    }
                    if(条件2){
                        condition2.await();
                    }
                } catch (InterruptedException e) {

                } finally {
                    condition1.signal();
                    condition2.signal();
                    lock.unlock();
                }
            }
        }

1.先看这个方法:lock.lock()

ReentrantLock类
public void lock() {
        sync.lock();
    }
NonfairSync 类中
  final void lock() {
    if (compareAndSetState(0, 1))
    setExclusiveOwnerThread(Thread.currentThread());
    else
      acquire(1);
  }
FairSync 类中
  final void lock() {
      acquire(1);
  }

公平锁和非公平锁中都实现了lock方法,公平锁直接调用AQS的acquire,而非公平锁先抢锁,抢不到锁再调用AQS的acquire方法进行上锁

进入acquire方法后的逻辑我们就都知道了。

2.再看这个方法lock.unlock()

public void unlock() {
        sync.release(1);
}

unlock方法内直接调用了AQS的Release方法进行解锁的逻辑,进入release方法后逻辑我们都已经知道了,这里不再往下跟。

3.最后看这个方法lock.tryLock()

这个方法中直接调用了sync中的nonfairTryAcquire方法,这个就是非公平锁实现的TryAcquire方法,只是名字改了而已。

tryLock方法的意思是尝试抢锁,是给程序员调用的尝试抢锁方法,如果抢锁成功返回true,抢锁失败返回false,当拿到抢锁失败的信号后,程序员可以做一些自己的策略。

如果没有tryLock方法,而是直接调用lock方法,就会走到acquire方法中,Acquire方法中也会调用TryAcquire方法,只不过在这里如果获取不到锁就会入队阻塞了,就没有程序员参与的可能了。

4.lock.newCondition();

在AQS那篇我们说过Condition是AQS中的条件队列,可以按条件将一批线程由不可唤醒变为可唤醒。

ReentrantLock类
 public Condition newCondition() {
        return sync.newCondition();
 }
sync静态内部类
final ConditionObject newCondition() {
            return new ConditionObject();
}

sync提供了创建Condition对象的方法,意味着ReentrantLock也拥有Condition的能力。

四、ReentrantLock和synchronized对比

我们下面说的ReentrantLock其实就是说AQS,因为它的同步实现主要在AQS里面。

1.实现方面

2.性能方面

优化前的synchronized性能很差,主要表现在两个方面:

ReentrantLock是在jdk实现的,它申请互斥量就是对锁标识state的争夺,它是通过cas方式实现。在java端实现。

对于争夺不到资源的线程依然要阻塞挂起,但凡阻塞挂起都要依赖于操作系统底层,这一步的用户态到内核态的切换是避免不了的。

因此在单线程进入代码块的时候,效率是很高的,因此我们说ReentrantLock性能高于原始的synchronized:

3.特性方面

两个都是常用的典型的独占锁。

4.使用方面

synchronized不需要手动释放锁,ReentrantLock需要手动释放锁,需要考虑异常对释放锁的影响避免异常导致线程一直持有锁。

以下是两个锁的使用方式:

class X {
            private final ReentrantLock lock = new ReentrantLock();
            Condition condition1=lock.newCondition();
            Condition condition2=lock.newCondition();
            public void m() {
                lock.lock();
                try {

                    if(1==2){
                        condition1.await();
                    }
                    if(1==3){
                        condition2.await();
                    }
                } catch (InterruptedException e) {

                } finally {
                    condition1.signal();
                    condition2.signal();
                    lock.unlock();
                }
            }
        }
class X {
            private final testtest sync=new testtest();;
            public void m() throws InterruptedException {
                synchronized(sync){
                    if(1==2){
                        sync.wait();
                    }
                    sync.notify();
                    sync.notifyAll();
                }
            }
        }

对比代码及特性说明:


当前文章:被问到ReentrantLock,你真的能答好吗?
网站地址:http://cdbrznjsb.com/article/cdsjjsj.html

其他资讯

让你的专属顾问为你服务