AQS与ReentrantLock理解

116 阅读8分钟

参考文献:

www.cnblogs.com/leesf456/p/… www.cnblogs.com/fsmly/p/112… ifeve.com/introduce-a… www.blogjava.net/zhanglongsr… www.cnblogs.com/waterystone…

基本概念

公平锁:多个线程竞争锁时需要排队

非公平锁:多个线程竞争锁时,先尝试插队,插队失败再排队

公平锁与非公平锁的区别:公平锁在获取锁的时候,尝试获取锁失败,会加入队列尾部(阻塞队列),待释放锁之后,队列首部获取锁; 非公平锁是不需要排队,直接抢占式获取锁;

可重入锁:一个线程的多个流程可以获取同一把锁

非可重入锁:一个线程的多个流程不能获取同一把锁

共享锁:多个线程能共享一把锁

排它锁:多个线程不能共享一把锁

Synchronized是可重入锁:虚拟机 ObjectMonitor.hpp定义了synchronized怎么实现重入锁 count+1

ReentrantLock是基于AQS实现的可重入锁,默认创建非公平锁

AQS的理解

AQS定义了一套多线程访问共享资源的同步器框架,是一个依赖状态(state)的同步器;

AQS具备特征:

  • 阻塞等待队列

  • 共享/独占

  • 公平/非公平

  • 可重入

  • 允许中断

Java.concurrent.util当中同步器的实现如Lock,Latch,Barrier等,都是基于AQS框架实现;

  • 一般通过定义内部类Sync继承AQS

  • 将同步器所有调用都映射到Sync对应的方法

AQS内部维护属性volatile int state (32位)

  • state表示资源的可用状态

AQS中定义的二种资源方式

Exclusive -- 独占模式,只有一个线程能执行,例如ReentrantLock

Share -- 共享模式,多个线程可以同时执行,例如Semaphore、CountDownLatch

AQS中定义的二种队列

-- 同步等待队列

-- 条件等待队列

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

-- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它

-- tryAcquire(int):(独占方式)尝试获取资源,成功则返回true,失败则返回false

-- tryRelease(int):(独占方式)尝试释放资源,成功则返回true,失败则返回false

-- tryAcquireShared(int):(共享方式)尝试获取资源 负数表示失败,0表示成功;但没有剩余可用资源;正数表示成功,且有剩余资源

-- tryReleaseShared(int):(共享方式)尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false

同步等待队列

条件等待队列

AQS源码理解

AQS类本身的属性

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

	private static final long serialVersionUID = 7373984972572414691L;
    
     //内部节点类,队列的实现(条件队列,等待队列)		
	static final class Node {
		//省略Node属性...
	}
        
     /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */    
    //指向同步等待队列的头节点
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    //指向同步等待队列的尾节点
    private transient volatile Node tail;

    //The synchronization state.  线程可用状态
    private volatile int state;
    
    //state的三种访问方式
    getState() 、setState()、compareAndSetState()
    
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
    	return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    
    //线程节点加入CLH同步队列
    private Node addWaiter(Node mode) {
            // 1. 将当前线程构建成Node类型
            Node node = new Node(Thread.currentThread(), mode);
            // Try the fast path of enq; backup to full enq on failure
            Node pred = tail;
            // 2. 1当前尾节点是否为null?
            if (pred != null) {
                // 2.2 将当前节点尾插入的方式
                node.prev = pred;
                // 2.3 CAS将节点插入同步队列的尾部
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            //如果尾节点不为null
            enq(node);
            return node;
        }
        
    private Node enq(final Node node) {
    	//循环处理
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                //队列为空需要初始化,创建空的头节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	//当前线程的前置节点指向尾节点
                node.prev = t;
                //set尾部节点
                if (compareAndSetTail(t, node)) {//当前节点置为尾部
                    t.next = node; //前驱节点的next指针指向当前节点
                    return t;
                }
            }
        }
    }
    
    //判断当前同步队列中是否含有等待获取锁的线程节点
    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;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
    //其它省略。。。。
}

Node构成的CLH队列

//内部节点类,队列的实现(条件队列,等待队列)		
static final class Node {
        
    //共享模式
    static final Node SHARED = new Node();
        
    //独占模式
    static final Node EXCLUSIVE = null;

    //线程结束
    static final int CANCELLED =  1;
    //等待被唤醒   
    static final int SIGNAL    = -1;
    //表示当前节点线程在条件队列中    
    static final int CONDITION = -2;
    //表示下一次共享式同步状态获取将会被无条件地传播下去  
    static final int PROPAGATE = -3;

	/**
    * 标记当前节点的信号量状态 (1,0,-1,-2,-3)5种状态
    * 使用CAS更改状态,volatile保证线程可见性,高并发场景下,
    * 即被一个线程修改后,状态会立马让其他线程可见。
    */
    volatile int waitStatus;

    //前驱节点指针,当前节点加入到同步队列中被设置
    volatile Node prev;

    //后继节点指针
    volatile Node next;

    //记录当前节点的线程
    volatile Thread thread;

   /**
    * 等待队列中的后继节点,如果当前节点是共享的,那么这个字段是一个SHARED常量,
    * 也就是说节点类型(独占和共享)和等待队列中的后继节点共用同一个字段。
    */
    Node nextWaiter;

    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
           throw new NullPointerException();
        else
           return p;
     }

     Node() {    // Used to establish initial head or SHARED marker
       }

     Node(Thread thread, Node mode) {     // Used by addWaiter
         this.nextWaiter = mode;
         this.thread = thread;
       }

     Node(Thread thread, int waitStatus) { // Used by Condition
         this.waitStatus = waitStatus;
         this.thread = thread;
       }
    }

ReentrantLock为什么可以实现加锁?

ReentrantLock内部维护了一个AQS对象,AQS对象中存在一个属性state,通过对象属性值的改变实现加锁与解锁;

public class ReentrantLock implements Lock, java.io.Serializable {
    
    private static final long serialVersionUID = 7373984872572414699L;
 
    private final Sync sync;

    //内部类,继承AbstractQueuedSynchronizer(抽象队列同步器)
    abstract static class Sync extends AbstractQueuedSynchronizer{
        abstract void lock();
    }
    
    //内部类的子类 非公平锁类
    static final class NonfairSync extends Sync {
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
       }
    
    //内部类的子类公平锁类
    static final class FairSync extends Sync {
           final void lock() {
            acquire(1);
        }
     }
    
    //本类的lock方法
    public void lock() {
        //调用内部类
        sync.lock();
    }
}

//TODO 利用多态,最终调用的是公平锁/非公平锁的lock方法
1、公平锁加锁的方式
 在加锁的适合会先尝试获取锁,如果自定义的加锁类没有重写尝试加锁的方法就会去调用             		 AbstractQueuedSynchronizer的tryAcquire方法,
	抛出异常throw new UnsupportedOperationException();

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
     }
    
    //TODO 调用AQS中acquire()
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
  //tryAcquire()尝试加锁,应该是模板方法设计模式,在父类中声明 子类实现
    addWaiter()线程节点加入CLH队列
   
  /**
  * 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;
             }
         }
            //如果是当前线程已经获取到锁 state = state + 1
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
 	
//尝试加锁的方式 
  -- 获取到线程的状态(默认初始化值为0)
  -- hasQueuedPredecessors()判断线程是否需要排队,需要排队就返回true,反之则是false
  -- compareAndSetState() 通常用于在获取到锁之前,尝试加锁时,对state进行修改,这种场景下,由于当前线程不是锁持有者,所以对state的修改是线程不安全的,也就是说可能存在多个线程都尝试修改state,所以需要保证对state修改的原子性操作,即使用了unsafe类的本地CAS方法;
  -- setState方法通常用于当前正持有锁的线程对state变量进行修改,不存在竞争,是线程安全的,所以此处没必要用CAS保证原子性,修改的性能更重要
  --(可重入锁)如果是当前线程已经获取到锁 state = state + 1
      
      
 2、非公平锁加锁的方式
 	static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        final void lock() {
        //多个线程竞争修改state 使用compareAndSetState()
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
最终调用的是父类Sync的nonfairTryAcquire()
   abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

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