ReentrantLock详解

上市

ReentrantLock是什么?        

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

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

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

顺便总结了几点synchronized和ReentrantLock的区别:

  • synchronized是JVM层次的锁实现,ReentrantLock是JDK层次的锁实现;
  • synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过ReentrantLock#isLocked判断;
  • synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;
  • synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以被中断的;
  • 在发生异常时synchronized会自动释放锁,而ReentrantLock需要开发者在finally块中显示释放锁;
  • ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活;
  • synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁(回顾一下sychronized的唤醒策略),而ReentrantLock对于已经在等待的线程是先来的线程先获得锁

 ReentrantLock的使用

同步执行,类似于synchronized

ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁  
ReentrantLock lock = new ReentrantLock(true); //公平锁  

//加锁    
lock.lock(); 
try {  
    //临界区 
} finally { 
    // 解锁 
    lock.unlock();  

测试:解决并发安全问题

public class ReentrantLockDemo {

    private static  int sum = 0;
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(()->{
                //加锁
                lock.lock();
                try {
                    for (int j = 0; j < 10000; j++) {
                        sum++;
                    }
                } finally {
                    // 解锁
                    lock.unlock();
                }
            });
            thread.start();
        }
        Thread.sleep(2000);
        System.out.println(sum);
    }

可重入

@Slf4j
public class ReentrantLockDemo2 {

    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();
        }
    }

可中断

@Slf4j
public class ReentrantLockDemo3 {

    public static void main(String[] args) {
        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执行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t1.interrupt();
            log.debug("线程t1执行中断");
        } finally {
            lock.unlock();
        }
    }

锁超时

立即失败

@Slf4j
public class ReentrantLockDemo4 {

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

        Thread t1 = new Thread(() -> {

            log.debug("t1启动...");
            // 注意: 即使是设置的公平锁,此方法也会立即返回获取锁成功或失败,公平策略不生效
            if (!lock.tryLock()) {
                log.debug("t1获取锁失败,立即返回false");
                return;
            }
            try {
                log.debug("t1获得了锁");
            } finally {
                lock.unlock();
            }

        }, "t1");


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

    }

公平锁

ReentrantLock 默认是不公平的

@Slf4j
public class ReentrantLockDemo5 {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock(true); //公平锁 

        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.debug(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "t" + i).start();
        }
        // 1s 之后去争抢锁
        Thread.sleep(1000);

        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    log.debug(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "强行插入" + i).start();
        }
    }

思考:ReentrantLock公平锁和非公平锁的性能谁更高?

        非公平锁的性能更高

条件变量      

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

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

@Slf4j
public class ReentrantLockDemo6 {
    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;
                //唤醒送烟的等待线程
                cigCon.signal();
            }finally {
                lock.unlock();
            }


        },"t1").start();

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

ReentrantLock源码分析

解锁过程

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

    //Sync继承AQS,拥有维护同步等待队列功能
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void 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;
        }
        //独占锁实现AQS的释放锁逻辑。
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;//将state变量减少releases
            if (Thread.currentThread() != getExclusiveOwnerThread())//如果不是当前占用锁线程来释放锁,则直接抛异常
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//释放锁成功
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);//重新设置state变量
            return free;//返回锁是否释放成功
        }

    }

    //非公平实现
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        //线程直接可以争抢锁资源,无需知道AQS维护的同步等待队列中是否有线程在等待唤醒
        final void lock() {
            if (compareAndSetState(0, 1))//CAS成功则直接获取锁资源
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    //公平实现
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        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;
        }
    }

    //默认非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    //通过传入boolean值来确定是否公平
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    //上来尝试去加锁
    final void lock() {
            acquire(1);
    }
    //调用AQSacquire()方法去获取锁,此时的tryAcquire()的方法调用的是FairSync子类重写的方法进行加锁
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    //公平锁获取锁过程
    protected final boolean tryAcquire(int acquires) {
            //当前运行线程
            final Thread current = Thread.currentThread();
            //获取state值
            int c = getState();
            //如果state值为0,说明没有其他线程加过锁
            if (c == 0) {
                //先去判断同步等待队列中是否有等待线程,有则占锁失败,否则进行CAS,CAS成功则占锁成功,并把当前线程设置为持有锁线程
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;//占锁成功
                }
            }
            //如果当前运行线程是占锁线程,则是可重入操作,体现了AQS的可重入特性
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;//将state值加acquires
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//重写设置state值
                return true;//占锁成功
            }
            return false;//占锁失败
        }
        
​
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        //这里不能直接判断头尾指针不相同来说明是否有等待的线程在同步队列中,因为在并发环境中,有可能有另外的线程现在在入队操作,这个线程封装成的Node节点的Pre指针指向头节点,但是头节点的Next指针还没有指向另外线程封装成的Node节点,所以还要判断在入队中的线程是否是当前线程
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

​
//竞争锁失败则需要进行入队操作 
private Node addWaiter(Node mode) {//mode为锁的性质,是否独占,还是共享
        Node node = new Node(Thread.currentThread(), mode);//把当前线程封装成Node节点
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;//获取尾节点
        if (pred != null) {//尾节点不为空直接进行入队操作
            node.prev = pred;//把当前线程封装成的Node节点的prev指针指向尾节点
            if (compareAndSetTail(pred, node)) {//考虑并发安全,需要CAS来设置尾指针指向当前节点
                pred.next = node;//把尾节点的next指针指向当前节点,当前节点成为新的尾节点
                return node;
            }
        }
        enq(node);//竞争失败或者队列为空都走该方法。
        return node;
    }
//进行队列的初始化,并进行入队操作
private Node enq(final Node node) {
        for (;;) {//入队失败,无限进行自旋,直到CAS入队成功
            Node t = tail;
            //如果还没有初始化
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))//创建一个虚节点,并把头指针指向该虚节点
                    tail = head;//使尾指针也指向该虚节点
            } else {//初始化完成后,还没有入队,进行入队操作
                node.prev = t;//获取尾节点
                if (compareAndSetTail(t, node)) {//使尾节点指向当前节点
                    t.next = node;//使前驱节点指向当前节点
                    return t;//返回旧尾节点
                }
            }
        }
    }
//入队后进行最后操作,判断是否需要阻塞,因为阻塞线程是重量级操作,所以尽可能的不进行阻塞线程,该方法还会进行一次尝试加锁,如果加锁还是失败,则进行阻塞线程,因为自旋太久会使CPU的资源占用率很高。
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;//用于判断该线程是否被中断
            for (;;) {
                final Node p = node.predecessor();//获取当前线程封装成的Node节点的前驱节点
,如果前驱节点为头节点,则进行去尝试加锁。
                if (p == head && tryAcquire(arg)) {
                    setHead(node);//
                    p.next = null; // help GC//把前驱节点的Next指针置空,等待GC时回收该垃圾
                    failed = false;
                    return interrupted;//该线程是否被中断
                }
                //如果还获取锁失败,则进行阻塞,阻塞前需要把前驱节点的waitStatus置为-1,用于后续进行唤醒,设置后再进行该线程的阻塞操作
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;//判断该阻塞线程中途是否被中断
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    //获取锁成功,重置头节点,进行出队操作
    private void setHead(Node node) {
        head = node;//使头节点指向当前节点
        node.thread = null;//把当前的thread置空
        node.prev = null;//把当前节点的Pre指针置空
    }
//设置前驱节点的WaitStatus值
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;//获取前驱节点的waitStatus值
        if (ws == Node.SIGNAL)//如果前驱节点的waitStatus值为-1,则可以直接进行park阻塞
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//设置前驱节点的waitStatus值为-1,再进行阻塞
        }
        return false;
    }
    //调用locksuport类的park()方法阻塞线程,并判断该线程是否被中断
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

打断

读者可能会比较好奇Thread.interrupted这个方法是做什么用的。

public static boolean interrupted() {  
    return currentThread().isInterrupted(true);  
}

这个是用来判断当前线程是否被打断过,并清除打断标记(若是被打断过则会返回true,并将打断标记设置为false),所以调用lock方法时,通过interrupt也是会打断睡眠的线程的,只是Doug Lea做了一个假象,让用户无感知。

但有些场景又需要知道该线程是否被打断过,所以acquireQueued最终会返回interrupted打断标记,如果是被打断过,则返回的true,并在acquire方法中调用selfInterrupt再次打断当前线程(将打断标记设置为true)。

解锁过程

    public void unlock() {
        sync.release(1);
    }
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    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;
    }
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

关注点:

1. ReentrantLock加锁解锁的逻辑

2. 公平和非公平,可重入锁的实现

3. 线程竞争锁失败入队阻塞逻辑和获取锁的线程释放锁唤醒阻塞线程竞争锁的逻辑实现设计的精髓:并发场景下入队和出队操作

你可能感兴趣的:(JUC,java)