java中ReentrantLock实现,公平锁和非公平锁,AQS并发队列,

Posted Leo Han

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中ReentrantLock实现,公平锁和非公平锁,AQS并发队列,相关的知识,希望对你有一定的参考价值。

一般在java中,遇到并发的时候,我们很多时候可能会使用synchronized关键字来实现锁,但是synchronized关键字有一定的缺陷(比如无法实现类似读锁、非公平),而Lock可以实现。在java中常用的有ReentrantLock,我们看下实现,一般我们在代码中如下方式来调用锁:

 ReentrantLock lock = new ReentrantLock();
lock.lock();
xxxxxx
 lock.unlock();

这里默认的是非公平锁,可以在构造ReentrantLock的时候,传入,指定是公平锁还是非公平锁:

 public ReentrantLock() 
        sync = new NonfairSync();
    
public ReentrantLock(boolean fair) 
        sync = fair ? new FairSync() : new NonfairSync();
    

我们来看看其实现机制。
在说这个之前,我们需要了解一个类AbstractQueuedSynchronizer,就是常说的并发工具 AQS,java中并发类很多都是基于AQS实现的。
这里说下AQS中几个比较重要的变量:

	// 请求链表头
    private transient volatile Node head;
    // 请求链表尾
    private transient volatile Node tail;

	// 当前锁被请求次数,为0时表示当前锁空闲 ,实现了可重入(通过state记录锁被请求多少次,只有当state=0的时候,表示锁被释放了)
    private volatile int state;

	// 这个是父类AbstractOwnableSynchronizer的变量,用来记录当前获取到锁的线程
 	private transient Thread exclusiveOwnerThread;

当我们调用lock.lock的时候:

  public void lock() 
        sync.lock();
    

而这里的sync则由两种实现,公平锁和非公平锁,我们先看看公平锁实现:

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

这里通过acquire获取资源量,每次请求都是1,首先进行tryAcquire,如果tryAcquire失败,那么acquireQueued添加到请求链表中,如果需要中断,那么会中断当前线程。

我们首先看看tryAcquire:

protected final boolean tryAcquire(int acquires) 
            final Thread current = Thread.currentThread();
            int c = getState();
            // c=0 表示当前锁空闲
            if (c == 0) 
            	// hasQueuedPredecessors判断当前是否有请求等待队列
            	// compareAndSetState如果没有等待队列,利用cas将state设置为当前请求量
            	// 如果cas设置成功,将exclusiveOwnerThread设置为当前线程
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) 
                    setExclusiveOwnerThread(current);
                    return true;
                
            
            // 如果getExclusiveOwnerThread为当前线程,表示当前线程已经获取到锁了,可重入,调整state
            else if (current == getExclusiveOwnerThread()) 
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            
            return false;
        

可以看到,tryAcquire 方法首先会判断锁的空闲状态,如果锁空闲,通过cas来获取锁,如果锁不空闲,判断是否是当前线程获取到了锁,如果是的话,那么更新state状态,否则返回false

如果tryAcquire返回false,那么会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)逻辑:

private Node addWaiter(Node mode) 
        Node node = new Node(Thread.currentThread(), mode);
        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 (;;) 
            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;
                
            
        
    
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主要是通过CAS将请求添加到请求链表的尾部,需要注意的是,传入的mode,·Node.EXCLUSIVE表示独占锁, Node.SHARED共享锁·然后在acquireQueued中,会for死循环来获取锁。而shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()则通过LockSupport.park来阻塞当前线程(这里是通过 LockSupport.park(this);来阻塞当前线程,必须等其他线程调用 LockSupport.unpark(hread);才能被唤醒)。
到这里公平锁获取锁的过程就完了,我们看下怎么释放锁的。

 public void unlock() 
        sync.release(1);
    
public final boolean release(int arg) 
        if (tryRelease(arg)) 
        	// 如果锁被释放 空闲,并且请求链表不为空,那么通过`LockSupport.unpark`唤醒请求链表头的线程。
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        
        return false;
    
protected final boolean tryRelease(int releases) 
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // c==0表示锁空闲,锁已经释放,重置exclusiveOwnerThread 为null
            if (c == 0) 
                free = true;
                setExclusiveOwnerThread(null);
            
            setState(c);
            return free;
        

可以看到,释放锁的时候,主要是调整了state状态值以及exclusiveOwnerThread,以及如果锁被释放空闲之后,会唤醒请求链表头部的请求来获取锁.

// NonfairSync
final void lock() 
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        
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;
        
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;
        

可以看到,获取锁的时候,对于非公平锁,上来就是直接获取锁,在获取不到锁的情况下,也是走acquire,但是也公平锁不同的是,非公平锁tryAcquire的时候,并不会关心当前请求队列是否有请求,而是直接通过cas并发获取锁。

这里我们总结下ReentrantLock公平锁和非公平锁的实现逻辑:

公平锁获取锁

1. 如果锁空闲,那么只有当请求链表为空的时候才会去获取锁,获取锁的时候通过cas设置state,并将exclusiveOwnerThread设置为当前线程
2. 如果当前锁的持有线程是当前线程,那么可以直接调整锁的持有量,将state设置为state+acquires,通过cas更新state
3. 如果上面不满足,那么会将当前请求放入到请求队列中 ,并通过LockSupport.park来阻塞请求线程
公平锁释放锁
1 首先通过cas将state线程持有量-acquires
2.  如果state==0,表名当前线程没有地方还需要锁,锁已经全部释放,锁空闲,设置exclusiveOwnerThread为空
3.  如果锁空闲(第二步成功),那么会通过LockSupport.unpark唤醒请求链表的头部线程
非公平锁获取锁
1. 公平锁首先是直接通过cas设置state状态,如果成功,表示获取锁成功(非公平锁不会去判断锁空闲的时候请求链表有没有请求)
2. 如果请求锁失败,那么通过`nonfairTryAcquire`去再次请求,在请求的时候如果锁空闲,不会判断请求链表有没有请求而是直接通过cas获取锁
3. 如果第二步失败,也公平锁一样,加入到请求链表中
非公平锁释放锁

非公平锁释放锁与公平锁一样,没有区别

在ReentrantLock中,获取锁的时候是通过cas设置state变量来实现的,state变量既用来实现锁的获取和释放,又结合exclusiveOwnerThread来实现可重入锁。
当state == 0的时候,表示锁空闲,获取锁通过cas设置state ,如果设置成功,则获取锁成功,结合exclusiveOwnerThread,如果exclusiveOwnerThread和当前线程相等,这表示当前线程已经获取到锁了,这时候在获取的话,给state增加accquires,当释放锁的时候,直接通过cas设置state为state - accquires,如果state == 0 则表明当前线程持有的锁变量全部释放完,锁空闲。

当线程获取不到锁的时候,通过LockSupport.park来阻塞线程进行等待,通过LockSupport.unpark来唤醒线程重新请求获取锁

以上是关于java中ReentrantLock实现,公平锁和非公平锁,AQS并发队列,的主要内容,如果未能解决你的问题,请参考以下文章

java多线程20 : ReentrantLock中的方法 ,公平锁和非公平锁

JAVA进阶之路-ReentrantLock的公平锁和非公平锁

深圳小公司面试题:AQS是什么?公平锁和非公平锁?ReentrantLock?

并发ReentrantLock中公平锁和非公平锁的理解

Java中的公平锁和非公平锁实现详解

理解ReentrantLock的公平锁和非公平锁