ReentrantLock源码分析

ReentrantLock源码分析

1.1、构造与使用

代码示例1:

	ReentrantLock lock = new ReentrantLock();
	try {
		lock.lock();
		// do somethings
	}finally {
		if(lock.isLocked())  {
			lock.unlock();
		}
	}

上面代码,共三句关键语句:
1、构造对象:new ReentrantLock();
2、获得锁:lock();
3、释放锁:unlock();
下面逐一解析。

1.2、解析 new ReentrantLock()语句

	private final Sync sync;
	
	public ReentrantLock() {
	   sync = new NonfairSync();
	}

从源码可看出,构造方法ReentrantLock()只完成了一件事,就是创建一个NonfairSync()对象,并赋值给sync变量。

sync是一个Sync类型变量,Sync类是ReentrantLock的抽象静态内部类,它有两个实现类,即:FairSync(公平锁)和NonfairSync(不公平锁)。

1.3、解析代码示例1中lock.lock()语句

	public void lock() {
	   sync.lock();
	}

从源码可看出,ReentrantLock.lock()方法的操作相当简洁明了,那就是调用sync对象的sync.lock()方法,通过1.1.1,我们知道sync此时是一个NonfairSync对象,那么真正的执行就要看NonfairSync.lock()方法,如下:

	final void lock() {
	   if (compareAndSetState(0, 1))
	       setExclusiveOwnerThread(Thread.currentThread());
	   else
	       acquire(1);
	}

lock方法语义有三步:

1、执行:compareAndSetState(0, 1);
2、成功则执行:setExclusiveOwnerThread(Thread.currentThread());
3、失败成执行:acquire(1);

解析1
先说结论,当前线程尝试获取锁,成功则返回true,失败返回false。 compareAndSet 简称CAS,是保证原子操作的一种机制,一般多用于实现乐观锁。具体实现是:判断指定的存储区域的值是否为0,如果是,则将该存储区域的值设置为1并且返回true,否则返回false。
源码如下:

	protected final boolean compareAndSetState(int expect, int update) {
	   // See below for intrinsics setup to support this
	   return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
	}

该方法直接调用了sum.misc.unsafe.compareAndSwapInt方法,这里不再展开,因为它是一个native方法,大概了解到它的代码语义为:在this[stateOffset]这个内存区域,如果值为0(这是初始值),则把它设置为1,并且返回true,否则啥也不做,然后返回false。注意,上述描述的过程是原子操作。

思考:compareAndSetState(0, 1)操作为什么能在多线程环境下获取锁?

解析2

解析:该方法很简单,源码如下:

	private transient Thread exclusiveOwnerThread;
	
	protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

只是简单地赋值语句。此时sync.lock()已执行完毕,没有任何阻塞,代码回到示例1 //do somethings 片断,继续完成相关业务。
那么就这么简单?
如果你觉得简单,只能说明你是幸运线程。假如两个线程同时执行一个同一个ReentrantLock的lock()方法,能直接进来的线程也只有一个而已,其余的都在解析1过程中,就已经失败了,流程都转到了解析3,那么现在看看解析3都干了些啥吧。

解析3
失败者的命运,直接看看看它们都经历了一些什么事吧~

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

关键在于tryAcquire(1)和acquireQueued(addWaiter(Node.EXCLUSIVE), 1)都干了些什么事儿。

1、tryAcquire是AbstractQueuedSynchronizer中定义的方法,由于我们sync的实例是NonfairSync,所以我们需要看NonfairSync对于tryAcquire的实现。NonfairSync.tryAcquire(1)源码如下:

	protected final boolean tryAcquire(int acquires) {
		return nonfairTryAcquire(acquires);
	}
	
	//看这里就好
	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;
	}

nonfairTryAcquire(1)中,调用了nonfairTryAcquire(1)方法,我们将注意力移到此方法,分析如下:
1、该方法有两个分支, 当状态为0时,进行CAS(上文已分析过该方法)操作,尝试获取锁,成功则执行setExclusiveOwnerThread方法(上文已分析过该方法),并返回true。失败则返回false。
2、另一个分支,判断当前线程是否为独占锁的拥有者(即执行了setExclusiveOwnerThread()),如果当前正在执行的线程,将状态加1,并执行setState,返回true。
getState()和setState()源码如下:

	private volatile int state;
	
	protected final int getState() {
		 return state;
	}
	
	protected final void setState(int newState) {
		state = newState;
	}

state初始为0。
我们回来看tryAcquire()整个代码含义是用于判断线程线程是否获得了锁,有锁则返回true,未获取到锁则返回false。

我们继续回到调用tryAcquire的地方:解析3
tryAcquire(1)如果返回true,表示锁在当前线程,可以继续往下执行,否则执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),源码如下:

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

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

先看看addWaiter()方法,以当前线程为参数,新建了一个Node结点,写入链表尾部(同样使用了CAS来保证线程安全),并返回。
再看下acquireQueued()方法,for()是一个无限循环,并且退出条件是"p == head && tryAcquire(arg)",即node为顶部结点,且获取锁成功。
如果不为顶部结点并且锁获取失败,则进入shouldParkAfterFailedAcquire()方法,源码如下:

	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	
  	  	//当前结点的前置结点状态,如果node第一次进入shouldParkAfterFailedAcquire方法, ws为0
        int ws = pred.waitStatus;

        if (ws == Node.SIGNAL)
        	//如果node是第二次进来,则返回true, 表示node需要休眠
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        	//将 node的前置结点pred的status设置Node.SIGNAL,返回false(表示node结点不需要休眠)
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

由shouldParkAfterFailedAcquire判断当前线程是否需要休眠。
parkAndCheckInterrupt()方法使当前线程休眠,并且返回当前线程的中断状态,如果当前线程已中断(注意什么情况下线程会被中断?一般而已是由外部不可抗拒因素导致线程中断标记被置为true,而LockSupport.park(this)方法并不会使线程Interrupt为true),则将interrupted设置为true。
我们再回头看看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)整个逻辑:
1、调用addWaiter方法,使用当前线程作为参数,生成一个node结点,并加入到等待队列。
2、调用acquireQueued方法,如果当前node在队列首部,则尝试获取锁,否则在自旋一次后就休眠,再被唤醒时,会检查当前线程的中断状态,如果当前线程已中断或被异常中断,并且未获得到锁,则会执行cancelAcquire(node)。源码如下:

	private void cancelAcquire(Node node) {
        if (node == null)
            return;

        node.thread = null;
        
        Node pred = node.prev;
        //丢弃队列中为CANCELLED状态的结点
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        Node predNext = pred.next;
        
        node.waitStatus = Node.CANCELLED;
		
		//将node结点的前置结点设置为尾结点
        if (node == tail && compareAndSetTail(node, pred)) {
        	//将前置结点的后置结点设置为null
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                //node出列
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }

cancelAcquire()方法主要目的是释放status为CANCELLED的结点,可以看到,如果node的前置结点为头结点,或者pred的status不为SIGNAL,则会执行unparkSuccessor(node)操作,源码如下:

 	private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        //如果状态为SIGNAL、CONDITION、PROPAGATE,则再次初始化为0
        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;
        }
        //唤醒后置结点的node
        if (s != null)
            LockSupport.unpark(s.thread);
    }

unparkSuccessor主要的功能就是唤醒node后置结点的线程。

总结

多个线程调用同一个ReentrantLock对象的lock()方法时, 只有一个线程能获取到锁,其余线程会再次尝试获取锁,失败后,会被添加到一个FIFO队列中,在该列队中,会从头部结点开始尝试获取锁,其余结点则在循环一次之后,便会休眠,等待唤醒。
在头部节点获取锁成功后,如果该线程的中断标记被置为了true,则会调用selfInterrupt()方法,设置当前线程的中断标记为true,然后继续执行该线程业务。
假如在获取锁的过程中,因为异常退出则会执行cancelAcquire()方法,用于清除为CANCELLED状态的结点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值