并发编程(5):ReentrantLock基本使用与实现原理

1、什么是ReentrantLock?

      ReentrantLock 是Java 中的一个锁机制的实现,用来保证并发场景下的线程安全问题,类似于synchronized,但是比synchronized性能要好。

 

2、ReentrantLock的基本使用

      使用方式1:lock() \ unlock()方法

/**
 * Java ReentrantLock 基本使用 ---》使用1000条线程让一个数加从0加增到1000,每条线程增加1。
 */
public class ReentrantLocakDemo {
    public static Integer count = 0;
    //构建一把可重入锁
    static ReentrantLock lock = new ReentrantLock();

    public static void incr(){
        lock.lock();   //获取锁
        count++;
        lock.unlock(); //释放锁
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i =0; i<1000; i++){
            new Thread(()->{
                incr();
            }).start();
        }
        Thread.sleep(5000L);
        System.out.println(count);
    }
}

      lock() 当前线程会去获取锁,如果获取成功就会执行业务代码,如果获取失败就会进入WAITING状态阻塞,直到获取到锁,如下代码可以验证---->未获取到锁会出现阻塞:

/**
 * Java ReentrantLock 操作 ,演示当T1线程获取到ReentrantLock后,T2线程再去获取锁,T2就会进入到waiting状态,等待获取锁。
 */
public class ReentrantLocakDemo1 {

    public static Integer count = 0;

    //构建一把可重入锁
    static ReentrantLock lock = new ReentrantLock();

    public static void incr() throws InterruptedException {
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"获取到锁");
        count++;
        Thread.sleep(5000L);
        lock.unlock();
        System.out.println(Thread.currentThread().getName()+"释放锁");

    }

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

        System.out.println("=============================================");
        new Thread(()->{
            try {
                incr();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();

        Thread.sleep(1000L);

        new Thread(()->{
            try {
                System.out.println("T2 start..");
                incr();
                System.out.println("T2 end..");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();

        Thread.sleep(10000L);
        System.out.println(count);
    }
}

           使用方式2:tryLock() \ tryLock(long timeout, TimeUnit unit) 方法

/**
 * Java ReentrantLock 操作
 *  演示 lock.tryLock(): 当前线程T1 tryLock() 成功后就获取到了锁。
 *                       此时T2也tryLock() 返回false,那么T2就会执
 *                       行到案例中的else,如果没有else 就会直接结束incr方法。
 *
 *                       同理tryLock(long time, TimeUnit unit) 差不多,只是获
 *                       取不到锁就会进入到TIMED_WAITING状态,持续时间为指定时间。
 */
public class ReentrantLocakDemo2 {

    public static Integer count = 0;

    //构建一把可重入锁
    static ReentrantLock lock = new ReentrantLock();

    public static void incr() throws InterruptedException {
        if (lock.tryLock(100,TimeUnit.SECONDS)){
            System.out.println(Thread.currentThread().getName()+"获取到锁");
            count++;
            Thread.sleep(50000L);
            System.out.println(Thread.currentThread().getName()+"释放锁");
        }else {
            System.out.println(Thread.currentThread().getName()+"没有获取到锁,没有执行+1 操作就结束了结束incr方法");
        }

    }

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

        System.out.println("=============================================");
        new Thread(()->{
            try {
                incr();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();

        Thread.sleep(1000L);

        new Thread(()->{
            try {
                System.out.println("T2 start..");
                incr();
                System.out.println("T2 end..");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();

        Thread.sleep(10000L);
        System.out.println(count);
    }
}

           tryLock()方法会立马返回是否获取到锁,tryLock(long timeout, TimeUnit unit)方法如果获取到锁立马返回true,如果不能立马获取到锁,阻塞指定时间后再去获取锁,如果此时白眉获取到就会快速返回false。

 

3、ReentrantLock实现原理

      3.1 我们先看看ReentrantLock类的结构

 

 

       3.2、lock()方法源码分析:

         lock.lock()为入口:

         lock()方法源码:

    public void lock() {
        //使用同步者来加锁
        sync.lock();
    }

         sync.lock()方法源码:由于我们使用的无参数构造器创建的ReentrantLock因此默认是非公平锁NonfairSync

            NonfairSync.lock() 源码:我们假设有三条线程去获取锁,我们以线程A先去获取锁。

        final void lock() {
            /*先用CAS操作去修改一个叫state的标记,这个state的标记在其父类
              AbstractQueuedSynchronizer(AQS)中。初始值是0 表示无锁,
              compareAndSetState(0, 1)这行代码的意思就是:
                                          如果 state=0,那就将其改为1,并返回true。
                                          如果state不等于0,那就直接返回false
            */
            if (compareAndSetState(0, 1))
                /*如果成功,说明获取到锁,那就将AbstractQueuedSynchronizer(AQS)父类 
                  AbstractOwnableSynchronizer中的exclusiveOwnerThread 的熟悉设置为当前线程,表 
                  示当前线程占领锁。
                */
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

              

                 threadA获取到锁后,我们假设threadB去获取锁,那么此时threadA没有释放锁,那么threadB执行                                                 compareAndSetState(0, 1)就会失败,那么就会执行到acquire(1)方法:

                 acquire(1)方法源码:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

           tryAcquire(int acquires)源码:

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

            nonfairTryAcquire(int acquires)源码:

        final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();

            //获取AQS中的state标记
            int c = getState();
            
            if (c == 0) {
                //如果标记为0那就再次获取锁
                if (compareAndSetState(0, acquires)) {
                    //再次获取锁成功后就设置独占的线程为当前线程
                    setExclusiveOwnerThread(current);
                    //返回true
                    return true;
                }
            }

            //判断是否是重入,如果是那就将设置state = state+1, 并发挥false
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }

            //如果再次尝试获取锁失败,并且也不是重入,那就返回false。
            return false;
        }

              

                addWaiter(Node.EXCLUSIVE)源码:

    private Node addWaiter(Node mode) {
        //使用当前的线程创建一个Node实例
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //判断AQS的尾节点tail是否为空。
        Node pred = tail;
        if (pred != null) {
            /*如果AQS的尾节点tail不为空,那就将当期的node添加到队列里面,就是将当前节点设置为尾节 
              点,将上一个尾节点设置为当前节点的前,将上一个尾节点的next设置为当前节点,就是构建一 
              个双向的列表关系
            */
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }

        //如果为空,那就初始化由链表实现的queue,并将第一个等待的线程node添加到队列
        enq(node);
        return node;
    }

              enq(final Node node)源码:

    private Node enq(final Node node) {
        //自旋
        for (;;) {
            Node t = tail;
            //如果父类AQS的queue的尾节点为null,那就初始化
            if (t == null) { // Must initialize
                //初始化的方式就是创建一个空node实例并使用CAS设置到父类AQS的头节点,如果设置头节 
                  点为这个空节点成功,那就将尾节点也设置为刚设置好的头节点。
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //当第二次循环的时候,就会进入到此处,此处会将当前的第一个需要等待的有当前线程的节 
                  点,添加到队列,并与之前的空node建立双向链表的关系。
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    //返回当前线程节点。
                    return t;
                }
            }
        }
    }

           我们假设的threadB是第一个需要等待的线程,threadB的node入队列后,lock中的情况如下:

               

                addWaiter(Node.EXCLUSIVE)执行结束后,lock的情况如上图,接下来就需要去阻塞threadB了,那即是                                        acquireQueued(final Node node, int arg)方法:

    final boolean acquireQueued(final Node node, int arg) {
        //入参的node为当前需要阻塞的线程node , arg=1
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋
            for (;;) {
                //获取当前线程node的前一个node,如果获取到的是AQS的head节点,那就在尝试去获取 
                  锁,由于此处是自旋,当当前线程使用parkAndCheckInterrupt阻塞后,占有锁的线程 
                  释放锁后,会唤醒头节点的下一个节点,如果此时头节点的下一个节点的线程后去到锁, 
                  就会将头节点的下一个节点设置为头节点,这样就可以让已经不用的节点移除队列了
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    //当前线程node的前一个node 是AQS的head节点,且当前线程尝试获取锁成功,那就
                      设置AQS的head节点为当前线程node。
                    setHead(node);
                    //将之前的头节点移除队列,此时会被GC回收
                    p.next = null; // help GC
                    failed = false;
                    //获取到锁了,说明没有被打断,那就返回false,不让线程自己打断。
                    return interrupted;
                }
                
                //当前线程node的前一个node不是AQS的head节点,或者当前线程尝试获取锁失败,
                  那就阻塞当前线程,会将当前线程节点的前一个节点的waitStatus修改为-1,在释放锁 
                  后,唤醒头节点的下一个节点的到时候会将头节点的waitStatus改为0
                  parkAndCheckInterrupt方法就会阻塞当前线程。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

                 修改head节点的waitStatus=-1

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * 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);
        }
        return false;
    }

                阻塞线程:

    private final boolean parkAndCheckInterrupt() {
        //使用LockSupport阻塞当前线程
        LockSupport.park(this);
        return Thread.interrupted();
    }

                阻塞当前线程后lock中情况如下,我们假设的threadB被阻塞了:

                   

                threadB被阻塞后,threadC调用lock.lock()方法且也被阻塞了,threadA还没有释放锁,就会变成如下情况:

                    

                    此时threadA释放锁了,且threadB获取到锁了,唤醒head节点的下一个嘛,那就是threadB,由于是非公平锁,如果                        刚好在threadA释放锁后,有一条线程threadD也去获取锁了,且获取到了,这里体现了非公平性

                       

 

                        接下来我们来分析lock.unlock()原理:

                        lock.unlock()源码:

   public void unlock() {
       //使用sync实例来释放锁
        sync.release(1);
    }

                          sync.release()源码:

    public final boolean release(int arg) {
        //尝试释资源 就是修改AQS中的state标记,以及AbstractOwnableSynchronizer类中的独占线程
        if (tryRelease(arg)) {

            //获取到head节点
            Node h = head;
            if (h != null && h.waitStatus != 0)
                // h.waitStatus != 0 表示在AQS的queue中存在排队等候的线程node。

                //唤醒head节点的下一个节点的线程,让其去争取锁。
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

                           tryRelease()源码:

        protected final boolean tryRelease(int releases) {
            //获取到AQS中的state标记 并减1,releases=1
            int c = getState() - releases;

            //如果当前释放资源的线程不是获取到锁的线程就抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;

            //处理重入次数,当c=0 就表示要释放锁了,那就把独占的线程设置为null
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            
            //重新设置state 标记,可能是重入次数-1,也可能是真正释放锁。
            setState(c);
            return free;
        }

                 unparkSuccessor(Node head)源码:

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.
         */
        //通过上面的跟踪,我们知道此处的node为head节点
        int ws = node.waitStatus;

        //我们在线程节点入队列的时候会将前一个节点的waitState设置为SINGAL(-1),那么在此处先将头节 
          点的waitState设置为0
        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.
         */
        //获取到head节点的下一个接点
        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)
            //如果不是脏节点,那就唤醒head节点的next节点中的线程。
            LockSupport.unpark(s.thread);
    }

                我们知道等待队列里的节点的线程都阻塞在acquireQueued(final Node node, int arg)方法的parkAndCheckInterrupt处

                 

            

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值