ReentrantLock源码分析

  前言
  
  之前这篇关于AQS的源码分析的文章:AbstractQueuedSynchronizer&CountDownLatch源码分析 不够详细,这篇文章再次从ReentrantLock出发来深入分析AQS的源码。可以先看看之前那篇文章大概了解一下AQS,再来看这篇文章。
  
  ReentrantLock加锁过程
  
  ReentrantLock默认使用非公平锁,这里先看非公平锁的实现。(后面再分析公平锁和非公平锁的区别)。
  
  情况1:第一个线程尝试加锁
  
  final void lock() {
  
  if (compareAndSetState(0, 1))
  
  setExclusiveOwnerThread(Thread.currentThread());
  
  else
  
  acquire(1);
  
  }
  
  先看compareAndSetState方法:
  
  //这个很明显了,state是AQS里面标识加锁状态的int字段。这里利用CAS无锁化机制,预期state值为0,并尝试将state值设置为1. 如果返回true,就说明现在没有人获得锁,当前线程成功将state设置为1了
  
  //state字段是volatile修饰的,为什么是用CAS去赋值,而不是直接给state赋值呢?我的理解是,如果多个线程同时给state赋值,那就麻烦了,state倒是给置为1了,但是到底是谁第一个赋值成功的呢?这个1到底算谁的呢?根本说不清啊。
  
  //所以你用CAS去赋值,没有并发冲突,你传给它原始值expect,要修改的值update,cpu底层会给你判断,现在内存里面是不是expect,是的话就给你修改成update,并且这个操作期间是没有人打断的。所以返回true的话你就肯定知道这个1是被我这个线程给设置的.
  
  protected final boolean compareAndSetState(int expect, int update) {
  
  // See below for intrinsics setup to support this
  
  return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
  
  }
  
  关于Unsafe放到最后面再详细解析。这里先不展开了。 然后就执行setExclusiveOwnerThread(Thread.currentThread());这句话:
  
  //这句话的意思就是将AQS的exclusiveOwnerThread属性设置为当前线程。 这个属性含义很明显,就是当前获得排他锁的线程呗.
  
  protected final void setExclusiveOwnerThread(Thread thread) {
  
  exclusiveOwnerThread = thread;
  
  }
  
  这种情况下,加锁过程就很简单了,无非就是把state设置为0,exclusiveOwnerThread设置为当前线程就可以了。
  
  情况2:第一个线程再次尝试获取锁(可重入锁的实现)
  
  final void lock() {
  
  //这种情况那么这个if条件显然就不成立了
  
  if (compareAndSetState(0, 1))
  
  setExclusiveOwnerThread(Thread.currentThread());
  
  else
  
  //程序会进入这里
  
  acquire(1);
  
  }
  
  进入acquire()方法看看:
  
  public final void acquire(int arg) {
  
  //预告一下,这种情况tryAcquire方法会返回true,意思就是加锁成功了,所以if不成立,不再继续往下执行
  
  if (!tryAcquire(arg) &&
  
  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  
  selfInterrupt();
  
  }
  
  再进入tryAcquire()方法:
  
  protected final boolean tryAcquire(int acquires) {
  
  //注意这里调用了调用了非公平锁的实现
  
  return nonfairTryAcquire(acquires);
  
  }
  
  final boolean nonfairTryAcquire(int acquires) {
  
  final Thread current = Thread.currentThread();
  
  //state字段是加了volatile的,所以拿的肯定是主内存中的最新值
  
  int c = getState();
  
  //这种情况c是等于1的,if不成立
  
  //还有为啥要再获取一次c,判断c是不是为0呢?因为整个过程是没有加锁的,那么可能刚才state!=0,你执行到这里,刚好这期间别人已经释放了锁,state又=0了,所以再判断一次.
  
  if (c == 0) {
  
  if (compareAndSetState(0, acquires)) {
  
  setExclusiveOwnerThread(current);
  
  return true;
  
  }
  
  }
  
  //进入了这个if条件
  
  else if (current == getExclusiveOwnerThread()) {
  
  int nextc = c + acquires;
  
  if (nextc < 0) // overflow
  
  throw new Error("Maximum lock count exceeded");
  
  //这里面就把state值+1了
  
  setState(nextc);
  
  return true;
  
  }
  
  return false;
  
  }
  
  所以可重入锁的实现也很简单,就是把state字段值+1了。现在state值就等于2了。
  
  情况3:第二个线程尝试加锁
  
  public final void acquire(int arg) {
  
  //这种情况tryAcquire肯定返回false了,会去看后面的一个条件
  
  if (!tryAcquire(arg) &&
  
  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  
  selfInterrupt();
  
  }
  
  先进入addWaiter方法:
  
  private Node addWaiter(Node mode) {
  
  //这里传入的Node.EXCLUSIVE实际上是null.
  
  //这里就是在构造一个Node节点,节点的nextWaiter属性就是这个mode,也就是null.
  
  Node node = new Node(Thread.currentThread(), mode);
  
  // Try the fast path of enq; backup to full enq on failure
  
  Node pred = tail;
  
  //一开始tail肯定是null,if不成立
  
  if (pred != null) {
  
  node.prev = pred;
  
  if (compareAndSetTail(pred, node)) {
  
  pred.next = node;
  
  return node;
  
  }
  
  }
  
  //走到这儿了
  
  enq(node);
  
  return node;
  
  }
  
  进入enq()方法:
  
  //这里方法很明显就是在初始化这个等待队列了
  
  private Node enq(final Node node) {
  
  for (;;) {
  
  Node t = tail;
  
  if (t == null) { // Must initialize
  
  //第一次循环,先尝试用CAS把head头结点设置为空Node.为啥要用CAS?还是一样的道理,现在有多个线程在一起执行这个代码,你用CAS才能知道这个值是被你设置成功的
  
  if (compareAndSetHead(new Node()))
  
  //把tail尾节点也指向头结点
  
  tail = head;
  
  } else {
  
  //第二次循环走到这里了。把当前节点的prev指向t
  
  node.prev = t;
  
  //现在尝试把tail指向当前node.注意要考虑并发的情况,所以要用cas
  
  if (compareAndSetTail(t, node)) {
  
  //head的next指向当前node
  
  t.next = node;
  
  return t;
  
  }
  
  }
  
  }
  
  }
  
  这个方法执行完之后,队列就是这样的:
  
  现在进入acquireQueued方法:
  
  final boolean a www.thd178.com cquireQueued(final Node node, int arg) {
  
  boolean failed = true;
  
  try {
  
  boolean interrupted = false;
  
  for (;;) {
  
  //获取当前节点的prev节点,这里就是head节点了
  
  final Node p = node.predecessor();
  
  //如果当前节点的前一个节点是头结点,再次尝试获取锁,这里目前是获取不到锁的
  
  if (p == head && tryAcquire(www.dasheng178.com arg)) {
  
  setHead(node);
  
  p.next = null; // help GC
  
  failed = false;
  
  return interrupted;
  
  }
  
  //走到这里来了.第一次循环shouldParkAfterFailedAcquire返回了fale,第二次循环走到这里,这个方法返回了true.会进入parkAndCheckInterrupt方法
  
  if (shouldParkAfterFailedAcquire(p, node) &&
  
  parkAndCheckInterrupt())
  
  interrupted = true;
  
  }
  
  } finally {
  
  if (failed)
  
  cancelAcquire(www.leyouzaixian2.com node);
  
  }
  
  }
  
  进入shouldParkAfterFailedAcquire(p, node)方法:
  
  private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  
  //没有给pread设置waitStatus,所以一开始默认就是0
  
  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 > www.boyunylpt1.com 0) {
  
  /*
  
  * Predecessor was cancelled. Skip over predecessors and
  
  * indicate retry.
  
  */
  
  do {
  
  node.prev = pred = pred.prev;
  
  } while (pred.waitStatus > 0);
  
  pred.next = node;
  
  } else {
  
  //把pread设置成了Node.SIGNAL状态
  
  compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  
  }
  
  //最后返回了false
  
  return false;
  
  }
  
  现在队列的状态是这样的:头结点的waitStatus被更新为SIGNAL.
  
  进入parkAndCheckInterrupt方法:
  
  private final boolean parkAndCheckInterrupt() {
  
  //这里调用LockSupport.park方法,意思就是把当前线程给阻塞住.所以这个线程就一直卡在这里了.什么时候会被唤醒呢? 直到获得锁的线程来唤醒他,这个后面在分析
  
  LockSupport.park(this);
  
  return Thread.interrupted();
  
  }
  
  所以说到这里第二个线程就被阻塞住了.不会再往下执行了
  
  情况4:第三个线程尝试获取锁
  
  public final void acquire(int arg) {
  
  //跟第二个线程一样,tryAcquire返回false,进入下一个条件
  
  if (!tryAcquire(arg) &&
  
  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  
  selfInterrupt();
  
  }
  
  进入addWaiter方法:
  
  private Node addWaiter(www.fengshen157.com/ Node mode) {
  
  Node node = new Node(Thread.currentThread(), mode);
  
  // Try the fast path of enq; backup to full enq on failure
  
  Node pred = tail;
  
  //tail节点已经有了,条件成立
  
  if (pred != null) {
  
  //当前节点的prev指向tail节点
  
  node.prev = pred;
  
  //把当前节点设置为tail节点
  
  if (compareAndSetTail(pred, node)) {
  
  //之前的tail节点的next指向当前节点
  
  pred.next = node;
  
  return node;
  
  }
  
  }
  
  enq(node);
  
  return node;
  
  }
  
  现在的队列状态如下:
  
  进入acquireQueued方法:
  
  final boolean acquireQueued(final Node node, int arg) {
  
  boolean failed = true;
  
  try {
  
  boolean interrupted = false;
  
  for (;;) {
  
  //获取当前节点的prev节点
  
  final Node p = node.predecessor();
  
  //当前节点的上一个节点,不是head,条件不成立
  
  if (p == head && tryAcquire(arg)) {
  
  setHead(node);
  
  p.next = null; // help GC
  
  failed = false;
  
  return interrupted;
  
  }
  
  //走到这里来了.第一次循环shouldParkAfterFailedAcquire返回了fale,第二次循环走到这里,这个方法返回了true.会进入parkAndCheckInterrupt方法
  
  if (shouldParkAfterFailedAcquire(p, node) &&
  
  parkAndCheckInterrupt())
  
  interrupted = true;
  
  }
  
  } finally {
  
  if (failed)
  
  cancelAcquire(node);
  
  }
  
  }
  
  后面跟第二个线程是一样的了。第三个线程也被阻塞住了。 现在的队列状态是这样的:
  
  也就是说,shouldParkAfterFailedAcquire这个方法的目的就是,每次有新的线程进入队列了,就把他的前一个节点的waiteStatus状态置为SINGAL(这有啥用?后面释放锁的时候就知道了).
  
  公平锁与非公平锁
  
  非公平锁: 根据前面非公平锁的代码分析,假设线程1持有锁,线程2和线程3阻塞等待。 线程1释放锁,正常应该是先把state状态改为0,exclusiveOwnerThread改为null,再去唤醒线程2. 但是假如再唤醒线程2之前,有个线程4突然冒出来尝试获取锁,发现state是0,就把锁给获取到了。 这就是非公平锁。 就是说每个线程获取锁并不完全是按照先来后到的顺序进行的,有可能后来的线程先获得了锁。
  
  公平锁: 公平锁就是每个线程就是先来后到,按照排队的顺序,一定是先排队的线程先获得锁。
  
  看一下公平锁是怎么实现的:
  
  final void lock() {
  
  //公平锁一上来直接进入了acquire方法。 不会像非公平锁那样一上来就先尝试一把看看能不能获得锁
  
  acquire(1);
  
  }
  
  进入acquire方法,这里是跟非公平锁一样的:
  
  public final void acquire(int arg) {
  
  //tryAcquire方法里面有点不一样,其他逻辑都是一样的
  
  if (!tryAcquire(arg) &&
  
  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  
  selfInterrupt();
  
  }
  
  进入tryAcquire方法:
  
  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;
  
  }
  
  进入hasQueuedPredecessors方法:
  
  //就是在判断等待队列中是否已经有线程了
  
  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;
  
  //这里我一开始很懵逼,head!=tail和head.next!=Thread.currentThread()我能理解能够说明当前线程中有人在等待。但是head.next=null是什么意思?为什么有这个判断??
  
  //后来我终于想明白了。去看一下enq方法里面初始化队列的逻辑:1.先初始化head,2.然后当前节点prev指向head,3.tail指向当前节点,4.最后是把head的next指向当前节点。 所以说当有一个线程走到第三步的时候,还没来得及把head.next执行自己,那么此时head.next是等于null的。 这个时候上一个线程其实已经入队了,所以这种情况就应该判断为队列中已经有线程了.
  
  //不得不佩服这里逻辑之缜密.
  
  return h != t &&
  
  ((s = h.next) == null || s.thread != Thread.currentThread());
  
  }
  
  tryLock给线程设置等待时间
  
  public boolean tryLock(long timeout, TimeUnit unit)
  
  throws InterruptedException {
  
  //这里把时间统一转为纳秒
  
  return sync.tryAcquireNanos(1, unit.toNanos(timeout));
  
  }
  
  进入tryAcquireNanos方法:
  
  public final boolean tryAcquireNanos(int arg, long nanosTimeout)
  
  throws InterruptedException {
  
  if (Thread.interrupted())
  
  throw new InterruptedException();
  
  //tryAcquire不用看了,直接看后面获取不到锁的情况
  
  return tryAcquire(arg) ||
  
  doAcquireNanos(arg, nanosTimeout);
  
  }
  
  进入doAcquireNanos方法:
  
  private boolean doAcquireNanos(int arg, long nanosTimeout)
  
  throws InterruptedException {
  
  if (nanosTimeout <= 0L)
  
  return false;
  
  final long deadline = System.nanoTime() + nanosTimeout;
  
  //将自己加入等待队列
  
  final Node node = addWaiter(Node.EXCLUSIVE);
  
  boolean failed = true;
  
  try {
  
  for (;;) {
  
  final Node p = node.predecessor();
  
  if (p == head && tryAcquire(arg)) {
  
  setHead(node);
  
  p.next = null; // help GC
  
  failed = false;
  
  return true;
  
  }
  
  nanosTimeout = deadline - System.nanoTime();
  
  //还没获得锁,赶紧返回false
  
  if (nanosTimeout <= 0L)
  
  return false;
  
  if (shouldParkAfterFailedAcquire(p, node) &&
  
  nanosTimeout > spinForTimeoutThreshold)
  
  //没什么花头,无非就只是阻塞操作设置一个超时时间而已,到时间就自动醒来了。LockSupport底层是调用Unsafe里面的本地方法,具体CPU怎么操作的,我也不知道
  
  LockSupport.parkNanos(this, nanosTimeout);
  
  if (Thread.interrupted())
  
  throw new InterruptedException();
  
  }
  
  } finally {
  
  if (failed)
  
  cancelAcquire(node);
  
  }
  
  }
  
  锁的释放过程
  
  public void unlock() {
  
  sync.release(1);
  
  }
  
  进入release方法:
  
  public final boolean release(int arg) {
  
  //如果这个线程上的锁重入了,假如加了两次锁。第一次tryRelease返回fasle,直接退出了。 只剩一把锁的时候,这里返回true,会进入if条件
  
  if (tryRelease(arg)) {
  
  Node h = head;
  
  //如果头结点不为null,并且头结点的状态不为0,才会去唤醒下一个线程。因为为0的话代表这个节点后面没有节点了.
  
  if (h != null && h.waitStatus != 0)
  
  unparkSuccessor(h);
  
  return true;
  
  }
  
  return false;
  
  }
  
  进入tryRelease方法:
  
  protected final boolean tryRelease(int releases) {
  
  int c = getState() - releases;
  
  if (Thread.currentThread() != getExclusiveOwnerThread())
  
  throw new IllegalMonitorStateException();
  
  boolean free = false;
  
  if (c == 0) {
  
  //如果c为0了,把exclusiveOwnerThread也改为null
  
  free = true;
  
  setExclusiveOwnerThread(null);
  
  }
  
  //如果是可重入锁,就只是把state值-1了而已,什么都没干,锁实际还没释放
  
  //我去,为啥这里设置state值的时候就不用CAS了??为啥??因为这个方法里面不会有并发冲突问题呀。只有获得锁的这个线程才可能执行tryRelease方法是不?而此时别的线程都在阻塞等待,不可能去改state的值。所以这里就没有并发冲突问题,所以可以直接设置值。
  
  setState(c);
  
  return free;
  
  }
  
  进入unparkSuccessor方法:
  
  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;
  
  //此时这个几点的waitStatue是SIGNAL,也就是-1
  
  if (ws < 0)
  
  //把这个节点的等待状态设置为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不为空,并且waitStatus为-1
  
  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);
  
  }
  
  总结
  
  关于CAS无锁化机制的理解
  
  在AQS的源码里面大量使用了CAS机制,所以说加锁性能比较好(实际上jdk1.6之后底层对sychronized底层做了优化,现在貌似sychronized和ReentrantLock性能是差不多的)。
  
  另外因为用了无锁化机制,那么代码里面就要经常注意,对共享变量的操作,共享变量使用valotile关键字修饰或者使用Atotic类,这样读的时候一定读的是最新值。 写的时候,你就要考虑是不是有并发冲突问题,有的话,你就用CAS的机制去修改。
  
  我们知道valotile修饰的变量是对线程立即可见的。 那么如果我们使用CAS去修改值的话,对别的线程也是立即可见的吗?应该不是的。因为AQS源码和Atomic类里面使用CAS机制修改的变量都是使用了valotile修饰的。也就是说使用CAS避免并发冲突,然后使用valotile保证线程立即可见。
  
  Unsafe类的理解
  
  UNsafe类我们的应用程序可以直接用么?不能!
  
  //私有构造函数,没法new
  
  private Unsafe() {}
  
  //这个方法你也没法调用,调用的话编译器就报错了
  
  //这个注解是啥意思?
  
  @CallerSensitive
  
  public static Unsafe getUnsafe()
  
  {
  
  Class localClass = Reflection.getCallerClass();
  
  if (!VM.isSystemDomainLoader(localClass.getClassLoader())) {
  
  throw new SecurityException("Unsafe");
  
  }
  
  return theUnsafe;
  
  }
  
  我们看AtomicLong的incrementAndGet方法:
  
  private static final long valueOffset;
  
  //valueOffset只的是这个类中value这是属性的内存地址偏移量。这个偏移量是在类初始化的时候就获取的
  
  static {
  
  try {
  
  valueOffset = unsafe.objectFieldOffset
  
  (AtomicLong.class.getDeclaredField("value"));
  
  } catch (Exception ex) { throw new Error(ex); }
  
  }
  
  private volatile long value;
  
  public final long incrementAndGet() {
  
  return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
  
  }
  
  再进入unsafe.getAndAddLong方法:
  
  public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)
  
  {
  
  long l;
  
  do
  
  {
  
  //先根据对象和属性的内存地址偏移量得到当前属性值
  
  l = getLongVolatile(paramObject, paramLong1);
  
  //然后再调用本地方法,判断内存中的值是不是l,如果是,就修改为l + paramLong2,否则的话,就死循环不断去获取,再尝试修改
  
  } while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));
  
  return l;
  
  }
  
  底层应该就是cpu根据对象的地址和偏移量直接定位到这个属性的内存位置去读写的。这个内存偏移量我是这么理解的:就是说你在类初始化的时候就计算出了这个偏移量。 因为类加载的时候你就是知道这个类有哪些属性,基本类型占去几个字节,引用类型占去8个字节,那么你就能算出来当你new出这个实例的时候,这个属性的内存地址相当于实例内存地址的偏移量了。
  
  另外我认为用这个偏移量去读取/修改属性值,可能改的工作内存中的值,而不一定是共享内存的值。因为源码里面CAS操作的共享变量都是加了volatile关键字的。 所以CAS和volatile一般就是结合起来操作变量的。不然你这个变量如果没有加volatile,那就很尴尬,如果你CAS比较的是工作内存,发现值没变,但共享内存的值其实已经变了,那就很尴尬了。
  
  LockSupport
  
  这个工具类就是提供了一系列park/unpark操作。用于阻塞/唤醒线程的。 那用这个park/unpark和wait/notify有什么区别?
  
  wait/notify是和synchronized结合使用的。阻塞期间还会释放锁。
  
  park/unpark这个不是和锁绑定的。你就是可以单独使用,park阻塞当前线程,然后别的线程可以调用unpark去唤醒那个线程。 park还

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值