ReentrantLock一探究竟

什么是ReentrantLock?

  对于锁(Lock,Synchronized等)相信大伙都不会陌生的吧,个人认为锁就为了保证数据的并发安全性(在存在线程竞争同意资源的时候),早在Lock工具类出来前在Java中的应用程序对于多线程的并发安全处理只能基于synchronized关键字来处理。那ReentrantLock是什么呢?包路径:java.util.concurrent.locks.ReentrantLock, 简称:JUC工具类
  显然是锁,感觉是废话,直接进入主题. Lock其实是一个接口,在JDK1.5以后开始提供,ReentrantLock对Lock接口进行了实现。ReentrantLock一般都会和Synchronized进行比较,其功能应该以及用法跟Sycnhronized 哪些不一样呢?后面我们会聊到的      

ReentrantLock能做什么?

废话不多说,首先来个Demo吧,快速入门,这样从实践中可能跟好的了解它,直接上代码,如下:
public class LockDemo {
    
    
   private static int num = 1; // 定义全局变量
   // 获取锁对象
   private static ReentrantLock lock = new ReentrantLock(); 
   public static void incrm(){
    
    
       try {
    
    
       // 加锁,针对在并发情况下可能导致数据的可见性等问题
           lock.lock();
           num++;
       } catch (Exception e) {
    
    
           e.printStackTrace();
       } finally {
    
    
          // 注意需要 在finally里面将锁释放
           lock.unlock();
       }
   }
   public static void main(String[] args) throws InterruptedException {
    
    
       LockDemo lockDemo = new LockDemo();
       // 模拟多线程进行修改值
       for(int i=0;i< 10;i++){
    
     
           new Thread(()->{
    
    
               lockDemo.incrm();
           }).start();
           Thread.sleep(100);
           System.out.println(num);
       }
   }
}
// 执行结果
2 3 4 5 6 7 8 9 10 11 

我对ReentrantLock的理解

跑完ReentrantLock的Demo第一感觉就是这个工具能保证在多线程情况下保证原子性,可见性的,但是它底层是如何实现呢?
接下了我们通过 lock.lock();为入口,去一探究竟,有大神说过:看源码呢,先七分猜测,在三分验证(通过猜测的话,就有个依据有个主线,至少知道每一步想要去干什么,如何实现的,就不会太盲目)。那么我们今天就先来猜测下ReentrantLock底层是怎么实现的呢

原理猜测

1、应该有状态state=0 ,默认0,有线性抢占到了变为 :1.
2、因为支持重入,那么必然有标识为重入次数的
3、既然可以重入,那么必然需要记录线程的相关信息的(如线程Id,状态等)
4、应该有个集合保存线程的(保存各个状态(wait,notify/notifyAll等)下的线程) 
等等...
好了,基于这些猜想,接下来我们一步步的进行验证吧..Go...Go...

验证

找到对应的验证入口,这一步很重要,前面说了这里就不说了,直接
   lock.lock(); // 验证入口
假设有ThreadA ,ThreadB,ThreadC 三个线程共同竞争
1、抢占到锁  假设A抢占到了锁 -- > tryAcquire() - > setExclusiveOwnerThread(current); cas操作获取的锁 compareAndSetState(0, acquires) ;那么state=0 -> 1 ;
    1.1 current == getExclusiveOwnerThread()  如果是同一线程,增加重入次数
2、没抢占到锁 ,即ThreadB,ThreadC  --> acquireQueued(addWaiter(Node.EXCLUSIVE), arg);  由于没抢占到锁,但后期还有机会去抢占锁,因此需要将这些线程都存起来,由AQS队列完成这个一操作。分下面几部
    1、构建队列(双向队列),addWaiter(Node.EXCLUSIVE)
    2、通过自旋构建头结点
    3、是否需要唤醒(不断的自旋去获取锁)
        3.1  p == head && tryAcquire(arg)  当前结点是头结点,并且获取到锁,将该结点切断,从AQS那拿走(p.next = null;  垃圾回收处理)返回复位位置。
        3.2  shouldParkAfterFailedAcquire(p, node)  && parkAndCheckInterrupt()
          3.2.1  shouldParkAfterFailedAcquire(p, node) 主要判断状态,将single的状态说明有条件可以获取锁,还将无效(cancel)kill掉(node.prev = pred = pred.prev;)并且将这些结点都cas赋值(SIGNAL状态)
          3.2.2 parkAndCheckInterrupt() 将线程挂起 LockSupport.park(this);
public class ReentrantLock implements Lock, java.io.Serializable {
    
    
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync; 
}
abstract static class Sync extends AbstractQueuedSynchronizer {
    
    
        private static final long serialVersionUID = -5179523762034025860L;
}

sync.lock(); //存在公平和非公平在这里插入图片描述
竞争锁,修改状态

static final class FairSync extends Sync {
    
    
        private static final long serialVersionUID = -3000897897090466540L;
        final void lock() {
    
    
            acquire(1);
        }
      }
      public final void acquire(int arg) {
    
    
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
 // 构建Node结点   
private Node addWaiter(Node mode) {
    
    
     Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
    
    
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
    
    
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
 // 构建空的头结点,  指定新的尾节点 
private Node enq(final Node node) {
    
    
        for (;;) {
    
     // 自旋2次,创建空的头结点
            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;
                }
            }
        }
    }   
 // 没抢占到锁,进入AQS队列中
 final boolean acquireQueued(final Node node, int arg) {
    
    
        boolean failed = true;
        try {
    
    
            boolean interrupted = false;
          for (;;) {
    
     // 自旋
                final Node p = node.predecessor();
                // 创建AQS的head,
                // 是头结点,并且获取到了锁
                if (p == head && tryAcquire(arg)) {
    
    
                    setHead(node); // 设为头结点
                   // help GC,拿出,将该结点拿出AQS队列
                    p.next = null; 
                    failed = false;
                    return interrupted; //返回复位信号
                }
                // 尝试获取锁
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true; // 挂起,
            }
        } finally {
    
    
            if (failed)
                cancelAcquire(node);
        }
    }

大致的流程图 (如有误,还望请大神指出)在这里插入图片描述

公平锁和非公平锁的区别

 主要区别 :非公平锁,一进来就先去抢占锁(tryAcquire(1)),详细见代码
 // 公平锁
final void lock() {
    
    
      // 老老实实进行排队去
       acquire(1);
  }
  // 非公平锁
final void lock() {
    
    
    // 先抢占,先试下
     if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
     else
      acquire(1);
}

ReentrantLock与Synchronized 的比较

相似性:都是通过加锁的方式,进行同步,即只要有一个进入加锁的模块(方法,或一段逻辑等),那么其他在进去就阻塞,等待唤醒才后才能再次进入。
功能区别: syn 是java的关键字,ReentrantLock是JUC工具类提供的API的互斥锁。当然就使用的便捷性来说,sync是绝对的方便的,ReentrantLock如果使用者忘记了释放锁了,造成了死锁就尴尬了。锁的细粒度来说ReentrantLock还是比Sync要优于些的
性能方便的区别:Sync在1.5及之前是没有优化的情况下,性能是很堪忧的,但是在1.6优化之后(新增了偏向锁,轻量级锁,重量级锁,通过锁的不断升级,包括也用了自旋,cas的手段去优化性能)还是很大的改善了的。目前这2种方式基本差不太多,官方还是建议用Sync。  哈哈哈...
还有一点:synchronized是非公平锁,而ReentrantLock是提供了公平和非公平锁的
见代码,如下:
 // 默认非公平的方式
public ReentrantLock() {
    
    
        sync = new NonfairSync();
    }
// 设置 公平/ 非公平
    public ReentrantLock(boolean fair) {
    
    
        sync = fair ? new FairSync() : new NonfairSync();
    }

猜你喜欢

转载自blog.csdn.net/u010200793/article/details/108563540