C/C++教程

ReentrantLock源码分析

本文主要是介绍ReentrantLock源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

  • 一. AQS
  • 二、总体结构
  • 三、加锁
    • 3.1 公平锁
    • 3.2 tryAcquire(arg) 图解
    • 3.3 入队 -> addWaiter(Node.EXCLUSIVE)
    • 3.4 addWaiter(Node.EXCLUSIVE)图示
    • 3.5 acquireQueued()
  • 三、解锁
  • 四.公平锁和非公平锁


一. AQS

  • ReentrantLock是基于AQS实现的,AQS即AbstractQueuedSynchronizer的缩写,这个是个内部实现了两个队列的抽象类,分别是同步队列和条件队列。
  • 其中同步队列是一个双向链表,里面储存的是处于等待状态的线程,正在排队等待唤醒去获取锁,条件队列不用管,ReentrantLock里面用的是同步队列。
  • AQS是一个抽象类,所以不能直接实例化,当我们需要实现一个自定义锁的时候可以去继承AQS然后重写获取锁的方式和释放锁的方式还有管理state,而ReentrantLock就是通过重写了AQS的tryAcquire和tryRelease方法实现的lock和unlock。

二、总体结构

  1. 首先要判断加进来的是公平锁还是非公平锁
  2. 为进来的线程加锁(state = 1)
  3. 如果前面有线程在等待获得锁,就进入队列(双向链表)
  4. 通过park()阻塞当前线程
  5. 释放锁 :一个线程出去了(unpark())。
  6. 注:只有一把锁
    在这里插入图片描述

三、加锁

首先,强调一点,ReentrantLock默认的构造器就是非公平锁。

    public ReentrantLock() {
        sync = new NonfairSync();
    }

3.1 公平锁

  • 通过源码可以找到ReentrantLock()中的lock方法
	final void lock() {
         acquire(1);
 	}
  • 再找到 acquire(1);方法
	public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • 可以看到,这里进行了一个判断。
	!tryAcquire(arg)的源码为
  • 以下代码的大概意思是判断当前对象的state值,看是否为1(判断是否加过锁,如果没有加过锁就加锁-> 就是把state 设置为1
  • 如果为1,则在判断下它是不是和当前的线程一样,如果和当前线程是同一个线程,则在state++,然后返回true
		//这里写一下state++,表示一下,看的清晰
		ReentrantLock lock = new ReentrantLock();
		//三次加锁 state = 3
		lock.lock();
		lock.lock();
		lock.lock();
		
		//三次解锁
		lock.unlock();
		lock.unlock();
		lock.unlock();
  • 如果上面的两者都不满足,就返回false(意思就是需要入队,就是要把当前线程加到同步队列里面去(就是那个双向链表))
  • 然后我在下面代码里写注释了,我有的一句话分两行写了
protected final boolean tryAcquire(int acquires) {
			//给当前线程赋值
            final Thread current = Thread.currentThread();
            //获得这个线程的state 这里getState()方法我也放下面了
            //state也放下面了
            int c = getState();
            //判断下获取到的state是不是0
            if (c == 0) {
            	//如果是零
            	// hasQueuedPredecessors()  判断它的前面是否有节点
            	// 如果没有节点 则 !hasQueuedPredecessors()就是true
            	//compareAndSetState(0, acquires))通过CAS的方式将state设为1
            	//如果成功将state设置为1,则compareAndSetState(0, acquires))
            	//返回true(这句话连着上句话)就是这个线程成功获得锁
            	//因为这里可能会有多个线程同时进来,所以要使用CAS
            	//如果多个线程来枪锁,只有一个能抢到,剩下的就返回false
            	//然后去入队了
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    //保证了这是第一个获得锁的线程,并且成功让他获得了锁
                    //这里获得锁的意思就是将state设置为1
                    //将其设置为当前获得锁的线程
                    setExclusiveOwnerThread(current);
                    //返回true
                    return true;
                }
            }
            //以下代码判断的是是否为重入,如果是重入的话就state++
            //最后返回true
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //如果既不是第一个获得锁的线程,也不是重入,返回false(需要入队)
            //就是等待锁被释放,之后这个线程在获得锁
            return false;
        }
    }
    //getState()
	protected final int getState() {
        return state;
    }
    //state 注 这里务必要用 volatile修饰,为的是保证其可见性
    private volatile int state;

3.2 tryAcquire(arg) 图解

在这里插入图片描述

3.3 入队 -> addWaiter(Node.EXCLUSIVE)

  • 首先,要清楚,既然能执行到这一步,那必然是加进来的既不是第一个,也不是重入
  • 下面。分析代码
  • 注:这里已经获得锁的线程不需要入队
	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;
            //通过CAS锁的方式让tail指向node
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //如果是插入的第一个结点,就执行enq(node);
        enq(node);
        return node;
    }
   
   private Node enq(final Node node) {
   		//for(;;)相当于while(true)
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
            	//这里 new 了一个结点 给 head
                if (compareAndSetHead(new Node()))	
                	//这里让 head 和 tail指向同一个结点
                	//然后继续循环
                	//注意,此时还没有用到 传进来的node           	
                    tail = head;
            } else {
                node.prev = t;
                //直到成功的将尾结点 tail通过CAS改为node
                //然后返回t
                //这里可能存在线程安全问题,所以用CAS
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    } 

3.4 addWaiter(Node.EXCLUSIVE)图示

1. 插入之前已经有线程在排队了
在这里插入图片描述
2. 第一个来排队的结点
在这里插入图片描述

3.5 acquireQueued()

  • 这个方法主要是为了阻塞
  • 首先判断这是不是第一个插入的结点,并且当前结点是否上锁
  • 如果是第一个结点,并且已经上锁了,就把当前结点设置为头节点,并阻塞
  • 如果不是,则执行
  • shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    //**************shouldParkAfterFailedAcquire(p, node)******************
	static final int SIGNAL    = -1;
		//总体来讲,就是要把前一个结点的waitStatus设置为-1
	 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    //**************parkAndCheckInterrupt()********************
    //这个方法就是通过park方法锁住当前线程
     private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

三、解锁

  • 这里解锁就是unpark(),但是要注意,这里解锁的话,在加锁哪里会有一个自旋操作
  • 下面是解锁的代码
	public void unlock() {
        sync.release(1);
    }
	public final boolean release(int arg) {
        if (tryRelease(arg)) {
        	//释放锁成功
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	//unpark();
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
     private void unparkSuccessor(Node node) {
     	//先释放头head
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        //释放第二个
        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);
    }
  • 下面这段代码就执行了
  • 下面这段代码会将已经获得锁的结点设置为头节点
	final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

四.公平锁和非公平锁

//*******************************公平锁******************************
	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;
        }
    }
//*******************************非公平锁******************************
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;
        }
  • 区别就是公平锁在获得锁的时候会判断当前是否在排队,如果在排队就入队
  • 非公平锁不会判断是否在排队,假设有一个锁恰好释放,并且有个线程刚好进来,这个刚进来的线程会和本应该获得锁的线程去争抢锁
这篇关于ReentrantLock源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!