深入理解独占锁ReentrantLock类锁

 ReentrantLock介绍

【1】ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。

【2】相对于 synchronized, ReentrantLock具备如下特点:

1)可中断 
2)可以设置超时时间
3)可以设置为公平锁 
4)支持多个条件变量
5)与 synchronized 一样,都支持可重入

 

ReentrantLock问题分析

【1】ReentrantLock公平锁和非公平锁的性能谁更高?

  1)那肯定是非公平锁,但是为什么是非公平更高呢?

  2)因为涉及到了线程的park()与unpark()操作,不管是ReentrantLock还是synchronized,都在避免这些操作。

    (1)如ReentrantLock的非公平同步器在得不到锁的情况下,即将要进入之前会再加一次锁,生成节点之后又会加一次锁,把节点放入队列之后又会加一次锁,最终迫不得已才会进行park()操作。

    (2)如synchronized,在生成monitor的过程之中也会多次尝试加锁,避免monitor的生成。

  3)为什么要避免呢?这就涉及到线程的概念了。

    (1)因为park()与unpark()操作涉及到了线程的上下文切换,同时又涉及到了时间片轮转机制

    (2)线程上下文切换,需要将旧的线程资源保存回内存【保存执行到了哪一步,需要什么东西】,将新的线程的资源加载入CPU,让新线程具备执行的资源并开始执行。但是这些操作都是需要花费时间的,会消耗一部分时间片的资源。如(这里仅仅只是举例说明),一个时间片本来就是50s,你拿到的时候花费了一定的时间(如10s)进行上下文切换,现在刚执行不到5s,你又要进行一次切换(又要花费10s)。那下一个拿到时间片的线程会不会还是会继续切换呢?而且你要下次运行就又要等时间片了。

    (3)所以说,本质上非公平机制是为了让持有CPU的线程尽可能多的做有用的任务,减少上线下文切换带来的开销,毕竟时间片来之不易,本身就是从众多线程之中好不容易分配得来的。

 

ReentrantLock的使用

【1】使用模板

Lock lock = new ReentrantLock();
//加锁
lock.lock();
try {
    // 临界区代码
    // TODO 业务逻辑:读写操作不能保证线程安全
} finally {
    // 解锁,放置在这里的原因是保证异常情况都不能干扰到解锁逻辑
    lock.unlock();
}

 

【2】可重入的尝试

public class ReentrantLockDemo {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }
    public static void method1() {
        lock.lock();
        try {
            log.debug("execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }
    public static void method2() {
        lock.lock();
        try {
            log.debug("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }
    public static void method3() {
        lock.lock();
        try {
            log.debug("execute method3");
        } finally {
            lock.unlock();
        }
    }
}

 

【3】中断机制尝试

  进行说明:这里面其实是main线程先获得了锁,所以t1线程其实是先进入队列里面,然后在main线程里面将t1设置为了中断。当main线程释放锁的时候,t1去加锁,发现自己被中断了,所以抛出中断异常,退出加锁。【其实这个中断加锁,怎么说,就是可以让失去加锁的权利,但是不影响你去排队】

@Slf4j
public class ReentrantLockDemo {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();

        Thread t1 = new Thread(() -> {

            log.debug("t1启动...");

            try {
                lock.lockInterruptibly();
                try {
                    log.debug("t1获得了锁");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("t1等锁的过程中被中断");
            }

        }, "t1");
        
        lock.lock();
        try {
            log.debug("main线程获得了锁");
            t1.start();
            //先让线程t1执行
            Thread.sleep(10000);

            t1.interrupt();
            log.debug("线程t1执行中断");
        } finally {
            lock.unlock();
        }

    }

}

 

【4】锁超时尝试

@Slf4j
public class ReentrantLockDemo {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();

        Thread t1 = new Thread(() -> {

            log.debug("t1启动...");
            //超时
            try {
                // 注意: 即使是设置的公平锁,此方法也会立即返回获取锁成功或失败,公平策略不生效
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("等待 1s 后获取锁失败,返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }

            try {
                log.debug("t1获得了锁");
            } finally {
                lock.unlock();
            }

        }, "t1");


        lock.lock();
        try {
            log.debug("main线程获得了锁");
            t1.start();
            //先让线程t1执行
            Thread.sleep(2000);
        } finally {
            lock.unlock();
        }

    }

}

 

【5】条件变量的尝试

  说明:

    1)java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用Condition.await() 方法使线程等待,其他线程调用Condition.signal() 或 Condition.signalAll() 方法唤醒等待的线程。

    2)由于可控的原因我们甚至可以多个条件队列来进行对线程调控。

  注意:调用Condition的await()和signal()方法,都必须在lock保护之内

@Slf4j
public class ReentrantLockDemo {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition cigCon = lock.newCondition();
    private static Condition takeCon = lock.newCondition();

    private static boolean hashcig = false;
    private static boolean hastakeout = false;

    //送烟
    public void cigratee(){
        lock.lock();
        try {
            while(!hashcig){
                try {
                    log.debug("没有烟,歇一会");
                    cigCon.await();

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            log.debug("有烟了,干活");
        }finally {
            lock.unlock();
        }
    }

    //送外卖
    public void takeout(){
        lock.lock();
        try {
            while(!hastakeout){
                try {
                    log.debug("没有饭,歇一会");
                    takeCon.await();

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            log.debug("有饭了,干活");
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockDemo6 test = new ReentrantLockDemo6();
        new Thread(() ->{
            test.cigratee();
        }).start();

        new Thread(() -> {
            test.takeout();
        }).start();

        new Thread(() ->{
            lock.lock();
            try {
                hashcig = true;
                log.debug("唤醒送烟的等待线程");
                cigCon.signal();
            }finally {
                lock.unlock();
            }


        },"t1").start();

        new Thread(() ->{
            lock.lock();
            try {
                hastakeout = true;
                log.debug("唤醒送饭的等待线程");
                takeCon.signal();
            }finally {
                lock.unlock();
            }


        },"t2").start();
    }

}

 

 

ReentrantLock源码分析(版本为jdk14)

【0】前置部分最好有关于JDK实现管程的了解【可查看  深入理解AQS–jdk层面管程实现

【1】ReentrantLock类自身部分

  0)继承关系

//锁的接口定义,定义了一个锁该具备哪一些功能
public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
}

  1)属性值

//同步器句柄,同步器提供了所有的实现机制
private final Sync sync;

  2)构造方法

//默认是采用非公平的同步器
public ReentrantLock() {
    sync = new NonfairSync();
}

//此外可以根据传入的参数选择同步器
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

  3)其他方法【看完之后,你会发现,其实ReentrantLock什么事都不干,统统都交给了持有的AQS同步器去干活了,有一种修饰器设计模式的味道,只是包装了一下,具体内部的同步器类型由自己选择,所以同步器显得就很重要

public class ReentrantLock implements Lock, java.io.Serializable {
    .....
    //获取锁定
    public void lock() {
        sync.lock();
    }

    //中断式的加锁,如果没有被中断就会加锁
    public void lockInterruptibly() throws InterruptedException {
        sync.lockInterruptibly();
    }

    //仅当在调用时锁不被另一个线程持有时才获取锁
    public boolean tryLock() {
        return sync.tryLock();
    }
   //超时加锁,限定加锁时间
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryLockNanos(unit.toNanos(timeout));
    }

    //尝试释放此锁
    public void unlock() {
        sync.release(1);
    }

    //返回一个用于此锁定实例的条件实例,说白了就是监视器
    public Condition newCondition() {
        return sync.newCondition();
    }

    //查询当前线程在此锁上保留的数量
    public int getHoldCount() {
        return sync.getHoldCount();
    }

    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }

    //判断同步器是否在被持有状态,也就是被加锁了
    public boolean isLocked() {
        return sync.isLocked();
    }

    //判断同步器的类型
    public final boolean isFair() {
        return sync instanceof FairSync;
    }

    protected Thread getOwner() {
        return sync.getOwner();
    }

    public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    //判断同步器里面是否有该线程
    public final boolean hasQueuedThread(Thread thread) {
        return sync.isQueued(thread);
    }

    public final int getQueueLength() {
        return sync.getQueueLength();
    }

    protected Collection<Thread> getQueuedThreads() {
        return sync.getQueuedThreads();
    }

    public boolean hasWaiters(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    //返回条件队列里面等待的线程的个数
    public int getWaitQueueLength(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    //返回条件队列里面等待的线程
    protected Collection<Thread> getWaitingThreads(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
    }


}

 

【2】抽象的Sync类部分

abstract static class Sync extends AbstractQueuedSynchronizer {
    //定义了核心的加锁逻辑
    @ReservedStackAccess
    final boolean tryLock() {
        Thread current = Thread.currentThread();
        //获取State属性值,这是在AQS里面定义的值,用于标记是否可以加锁,0代表没有人在用锁,1代表有人在占用,大于1说明这个锁被这个人加了多次【即重入锁概念】
        int c = getState();
        if (c == 0) {
            //CAS保证只有一个人能成功
            if (compareAndSetState(0, 1)) {
                //设置持有锁的线程
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (getExclusiveOwnerThread() == current) { //走到这里说明有人持有了锁,但是可以判断持有的人是不是自己【可重入】
            if (++c < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            //因为每一次重入都会导致State的值+1,所以解锁的时候对应要减1
            setState(c);
            return true;
        }
        return false;
    }

    //为子类留下的加锁逻辑的抽象方法
    abstract boolean initialTryLock();

    //核心加锁逻辑里面便是使用抽象方法进行加锁
    @ReservedStackAccess
    final void lock() {
        if (!initialTryLock())
            acquire(1);
    }

    @ReservedStackAccess
    final void lockInterruptibly() throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!initialTryLock())
            acquireInterruptibly(1);
    }

    @ReservedStackAccess
    final boolean tryLockNanos(long nanos) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return initialTryLock() || tryAcquireNanos(1, nanos);
    }

    //尝试释放锁
    @ReservedStackAccess
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (getExclusiveOwnerThread() != Thread.currentThread())
            throw new IllegalMonitorStateException();
        boolean free = (c == 0);
        if (free)
            setExclusiveOwnerThread(null);
        setState(c);
        return free;
    }

    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // Methods relayed from outer class

    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }

    final boolean isLocked() {
        return getState() != 0;
    }

    /**
     * Reconstitutes the instance from a stream (that is, deserializes it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

 

【3】实现抽象的 Sync类 的公平锁 FairSync类部分

static final class FairSync extends Sync {
    //尝试锁定方法
    final boolean initialTryLock() {
        Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //看得出来首先队列要为空,其次才是CAS加锁成功,才算能够持有锁 
            //也就是说队列不为空,连CAS加锁的资格都没有,所以十分公平
            if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (getExclusiveOwnerThread() == current) {
            if (++c < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(c);
            return true;
        }
        return false;
    }

    //尝试获取方法
    protected final boolean tryAcquire(int acquires) {
        if (getState() == 0 && !hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}

//AbstractQueuedSynchronizer类#hasQueuedThreads方法
//判断队列是否为空【由于AQS里面采用的是链表实现队列效果,所以是判断节点情况】
public final boolean hasQueuedThreads() {
    for (Node p = tail, h = head; p != h && p != null; p = p.prev)
        if (p.status >= 0)
            return true;
    return false;
}

 

【4】实现抽象的 Sync类 的非公平锁 NonfairSync类部分

//与公平的同步器进行比较的话,会发现,他们本质没什么区别,因为大多数走的都是抽象方法的逻辑和AQS的方法
//最大的区别在于加锁的方式不同,公平方式,队列没人才去加锁;非公平方式,不管队列有没有人,都是直接去加锁,加到了就持有
static final class NonfairSync extends Sync {
    //尝试锁定方法
    final boolean initialTryLock() {
        Thread current = Thread.currentThread();
        //直接尝试CAS获取加锁权利
        if (compareAndSetState(0, 1)) { // first attempt is unguarded
            setExclusiveOwnerThread(current);
            return true;
        } else if (getExclusiveOwnerThread() == current) {
            int c = getState() + 1;
            if (c < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(c);
            return true;
        } else
            return false;
    }

    //尝试获取方法
    protected final boolean tryAcquire(int acquires) {
        //判断是否有人持有锁,没有则去加锁
        if (getState() == 0 && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}