Java并发编程之ReentrantLock源码解析详解编程语言

一、关于ReetrantLock

在上篇文章Java并发编程之AQS中,比较详细的说了一下关于AQS的设计和AQS的代码相关原理。在上篇文章中也说了,AQS是J.U.C的核心,是用来构建锁或其他同步组件的基础框架,这其中就包括了ReentrantLock。由于前面已经详细的说了AQS的原理,在本篇文章中,对于涉及到AQS相关的东西,便会一带而过。

Java并发编程之ReentrantLock源码解析详解编程语言

ReentrantLock和synchronized一样都是具有重入的功能,但相比synchronized而言,ReentrantLock的功能显得更丰富。从上面图片中,可以知道ReentrantLock实现了Lock接口和Serializable,则说明ReentrantLock具有Lock接口的所有实现和序列化的功能。从下面图片中,同样可以看出关于Lock接口的所有功能。

Java并发编程之ReentrantLock源码解析详解编程语言

上面说了关于ReentrantLock的相关设计,那么下面就开始ReentrantLock的主要实现吧!

二、ReetrantLock的实现

在说ReentrantLock具体实现之前,先大体说下关于ReentrantLock的一个简单的工作流程:

  • 在ReentrantLock中同样是维护着一个state变量(因为ReentrantLock类中有一个Sync抽象类,而Sync正是继承着了AbstractQueuedSynchronizer,也就是我们上篇文章所说的AQS,具体的后面会详说),这个state的初始化值为0,表示未锁定状态。
  • A线程lock()时,会调用tryAcquire()(公平与非公平都是如此)独占锁,并将state+1
  • 此后,其他线程再调用tryAcquire()的时候就会失败,直到A线程unlock()到state=0(即释放锁)为止,其他线程便可以获取该锁了
  • 当然,在释放之前,A线程自己可以重复获取此锁的(state变量的累加),这就是重入的概念
  • 但要注意,获取多少次就必须要释放多少次,这样才能保证state恢复到0的状态

简单介绍完ReentrantLock的工作流程,现在开始正式进入到ReentrantLock类中的代码解析了。

 private final Sync sync;

在ReentrantLock类中,有一个核心的成员变量(如上代码所示),这是一个同步的变量,前面亦曾提及。这个类使用abstract修饰着,这证明着它的下面肯定会有这具体的实现,这个类的具体实现由两种方式:FairSync和NonfairSync(如下图所示)。这说明在使用ReentrantLock的时候,可以添加一个参数,来说明你是使用公平锁的方式还是使用非公平锁的方式。

Java并发编程之ReentrantLock源码解析详解编程语言

从上图中,可以看到Sync类继承了AQS,既然继承了AQS,也就说明Sync 具有AQS的所有的特性。接下来看下Sync类的具体代码:

    abstract static class Sync extends AbstractQueuedSynchronizer { 
        private static final long serialVersionUID = -5179523762034025860L; 
 
        /** 
         * 抽象类,具体实现有fair和nonfair  
         */ 
        abstract void lock(); 
 
        /** 
         * 非公平锁尝试获取许可的方法 
         */ 
        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 tryRelease(int releases) { 
            int c = getState() - releases; 
            if (Thread.currentThread() != getExclusiveOwnerThread()) 
                throw new IllegalMonitorStateException(); 
            boolean free = false; 
            if (c == 0) { 
                free = true; 
                setExclusiveOwnerThread(null); 
            } 
            setState(c); 
            return free; 
        } 
 
        // 判断当前的线程是否是独占的 
        protected final boolean isHeldExclusively() { 
            // 判断当前线程是否与getExclusiveOwnerThread()相等,如果相等则是,反之不是。 
            return getExclusiveOwnerThread() == Thread.currentThread(); 
        } 
 
        //  
        final ConditionObject newCondition() { 
            return new ConditionObject(); 
        } 
 
        // 获取线程本身,首先getState() == 0,就返回一个null,不等0就放回当前这个独占的线程 
        final Thread getOwner() { 
            return getState() == 0 ? null : getExclusiveOwnerThread(); 
        } 
 
        final int getHoldCount() { 
            return isHeldExclusively() ? getState() : 0; 
        } 
 
        // 判断是否上锁 
        final boolean isLocked() { 
            return getState() != 0; 
        } 
 
        private void readObject(java.io.ObjectInputStream s) 
            throws java.io.IOException, ClassNotFoundException { 
            s.defaultReadObject(); 
            setState(0); // reset to unlocked state 
        } 
    }

在说完ReentrantLock顶层的一些实现后,现在开始说一下它的具体的一些实现了。

公平锁

公平锁的UML结构图如下:

Java并发编程之ReentrantLock源码解析详解编程语言

    ReentrantLock reentrantLock = new ReentrantLock(true); 
 
    // 因为传入的是true,所以会new FairSync() 
    public ReentrantLock(boolean fair) { 
        sync = fair ? new FairSync() : new NonfairSync(); 
    }
    static final class FairSync extends Sync { 
        private static final long serialVersionUID = -3000897897090466540L; 
 
        // 公平锁,默认传值为1 
        final void lock() { 
            acquire(1); 
        } 
 
        /** 
         * Fair version of tryAcquire.  Don't grant access unless 
         * recursive call or no waiters or is first. 
         */ 
        protected final boolean tryAcquire(int acquires) { 
            // 把当前线程赋值给current 
            final Thread current = Thread.currentThread(); 
            // 获取state 
            int c = getState(); 
            // 如果等于0,说明当前没有任何线程去获取资源,那么就可以去获取锁了 
            if (c == 0) { 
                /** 
                 * 1.hasQueuedPredecessors():判断这个队列里前面有没有排队等待的线程,如果      
                 *   有等待则返回true,反之为false 
                 * 2.如果前面没有等待的线程,那么就可以继续调用CAS操作,这个操作就是对state进 
                 *   行+1,CAS操作是保证多线程操作不会产生线程安全问题的,如果CAS操作成功,将 
                 *   会进行下一步 
                 * 3.调用setExclusiveOwnerThread方法,设置当前线程为独占的 
                 */ 
                if (!hasQueuedPredecessors() && 
                    compareAndSetState(0, acquires)) { 
                    setExclusiveOwnerThread(current); 
                    return true; 
                } 
            } 
            // 如果当前线程引用和当前的线程相匹配,说明是同一个线程,就可以再次获取这把锁(重入) 
            else if (current == getExclusiveOwnerThread()) { 
                // 将c和新传入的中进行相加 
                int nextc = c + acquires; 
                if (nextc < 0) 
                    throw new Error("Maximum lock count exceeded"); 
                // 将nextc赋值到state变量中 
                setState(nextc); 
                return true; 
            } 
            return false; 
        } 
    } 

上面代码中,如果要加锁的话会调用acquire(1)方法,这里是直接传入1的,因为这个FairSync 是继承自Sync的,而Sync又是继承自AQS的,所以这里的acquire方法时调用AQS中的方法的,看下面的代码一中的代码,这里首先会调用!tryAcquire(arg)方法尝试获取锁,如果获取成功直接调用selfInterrupt()方法(这个方法的作用是打断自己本身),如过获取成功,那将应该打断自己唤醒自己,继续去做一些 后续的事情。如果没有获取成功,那肯定是处于排队等待状态。当然没有获取成功它将会首先去调用addWaiter(Node.EXCLUSIVE),把当前这个线程加入到FIFO双线链表队列中,并且设置这个Node为EXCLUSIVE(独占的),然后调用acquireQueued方法使线程在等待队列中获取资源(自旋),一直获取到资源后才返回。这段代码具体可以去看上篇文章。

对于上面说到的!tryAcquire(arg)方法,如果点进去,你会发现如代码二中所示,这里会说不做具体实现,这里肯定是交由子类去实现的,因为这里说的是公平锁,那么tryAcquire方法的实现,就是上面那段代码中的tryAcquire方法了。

代码一:

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

代码二:

    protected boolean tryAcquire(int arg) { 
        throw new UnsupportedOperationException(); 
    }

非公平锁

非公平锁的UML结构图如下:

Java并发编程之ReentrantLock源码解析详解编程语言

说完了公平锁,下面来说下非公平锁,其实在ReentrantLock中默认是为非要公平锁的,只有在新建ReentrantLock时传入true时才为公平锁。下面首先看下代码:

    static final class NonfairSync extends Sync { 
        private static final long serialVersionUID = 7316153563782823691L; 
 
        final void lock() { 
            if (compareAndSetState(0, 1)) 
                setExclusiveOwnerThread(Thread.currentThread()); 
            else 
                acquire(1); 
        } 
 
        protected final boolean tryAcquire(int acquires) { 
            return nonfairTryAcquire(acquires); 
        } 
    }

相比较公平锁而言,公平锁的实现,是简单的多了,在非公平锁的代码中,如果当前是非公平锁,就会直接进行CAS操作,就相当于当前线程进来后,不需要排队了,直接进行CAS操作,哪个线程CPU优先分配给它了,就直接对它进行原子操作。也就是谁成功了,谁就去获得这把锁,然后就把当前线程设置为独占。

获取锁

一般获取锁的方式如下:

        ReentrantLock reentrantLock = new ReentrantLock(); 
        reentrantLock.lock();

lock()方法:

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

前面说了Sync是ReentrantLock中的一个抽象内部类,而且他继承了AQS,且有两个子类FairSync和NonfairSync。则说明ReentrantLock中的大部分功能是由Sync去实现的。在Sync中定义了内部方法lock(),如图:

Java并发编程之ReentrantLock源码解析详解编程语言

对于公平锁和非公平锁具体的获取方式前面已经进行说明了,这里就不再进行说明了。

锁释放

获取锁之后,自然是要进行释放的,释放方法如下:

    public void unlock() { 
        sync.release(1); 
    }

这里释放锁的还是调用Sync中的release,release(1)方法的具体代码如下:

    public final boolean release(int arg) { 
        if (tryRelease(arg)) { 
            Node h = head; 
            if (h != null && h.waitStatus != 0) 
                unparkSuccessor(h); 
            return true; 
        } 
        return false; 
    }

在上面的代码中,与获取锁的方法相似,也会首先调用tryRelease(int arg):

        protected final boolean tryRelease(int releases) { 
            // 首先用getState()到的值减去传入的releases 
            int c = getState() - releases; 
            // 然后判断,如果当前线程不是被独占的,就抛异常 
            if (Thread.currentThread() != getExclusiveOwnerThread()) 
                throw new IllegalMonitorStateException(); 
            //  
            boolean free = false; 
            if (c == 0) { 
                free = true; 
                // 将独占置为空 
                setExclusiveOwnerThread(null); 
            } 
            // 把减完后的值赋给state 
            setState(c); 
            return free; 
        }

ReentrantLock与synchronized的区别

区别如下:

  • synchronized是关键字,ReentrantLock是类
  • ReentrantLock可以获取锁的等待时间进行设置,避免死锁
  • ReentrantLock可以获取各种锁的信息
  • ReentrantLock可以灵活的实现多路通知
  • 机制:sync操作Mark Word,lock调用Unsafe类的park()方法

版权声明:尊重博主原创文章,转载请注明出处:IT虾米网

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/19410.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论