ReentrantLock重入锁

  上次博客讲到了通过wait()方法和notify()方法来实现循环打印数字和字母得问题。其实使用重入锁也可以实现同样得功能,那么开始我们先通过源码来了解一下重入锁把。

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

首先它有一个lock()方法,它用来加锁,从代码中可以看到,它调用得是sync.lock()方法,


public class ReentrantLock implements Lock, java.io.Serializable {    private static final long serialVersionUID = 7373984872572414699L;    /** Synchronizer providing all implementation mechanics */    private final Sync sync;

/**     * Base of synchronization control for this lock. Subclassed     * into fair and nonfair versions below. Uses AQS state to     * represent the number of holds on the lock.     */    abstract static class Sync extends AbstractQueuedSynchronizer {        private static final long serialVersionUID = -5179523762034025860L;

/**         * Performs {@link Lock#lock}. The main reason for subclassing         * is to allow fast path for nonfair version.         */        abstract void lock();
 

在这个类里面,有一个静态抽象类Sync对象以及Sync得属性,因此我们可以知道它调用得是Sync里面得lock()方法,而Sync又是一个抽象类,lock()方法也是一个抽象方法,具体由它得子类去实现。

然后我们接着看ReentrantLock得构造方法

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

通过代码可以看到,在初始化得时候会初始化Sync对象,通过代码可以看出如果不带参数得话默认使用得是NonfairSync这个子类,也可以指定使用FairSync这个子类。好了,通过以上代码我们可以知道,ReentrantLock这个类会在实例化得时候指定FairSync或者NonFairSync,下面我们来介绍一下这两个类。首先通过字面意思可以看出前者得意思是“公平锁”,后者得意思是“非公平锁”。那么为什么要这么叫呢,其实是因为他们得lock()实现方法得差异。下面我们就来一一介绍,首先介绍FairSync

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

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

从代码可以看出最后得实现是acquire(1)这个方法,那么这个方法干了什么呢?看代码其实挺少得,其实干得事情并不少。首先会执行tryAcquire(arg)这个方法。同样,看代码。

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

我们一步步来看,首先获取当前得线程,然后或者状态c,这个c很重要,它得含义就是这个“重入”得含义,等会再说。getState()方法通过代码可以看出返回得是ReentrantLock里面得state属性,因为是int类型,所以默认为0,代表没有线程正在使用它,这里讲了c这个变量得含义,我们接着往下看,如果c等于0,也就是说没有线程正在使用,那么他会进入下一个if判断,首先会执行hasQueuedPredecessors()方法。同样继续点进去看代码实现

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

这个方法是AbstractQueuedSynchronizer这个类里面得方法,通过代码可以看出这个类里面维护了一个FIFO队列,队列中每一个元素都是一个Node节点,其中Node对象有prev属性用来表示前一个节点,next属性用来表示下一个节点,thread属性用来标识当前线程,队列有一个head(头)节点和tail(尾)节点,head节点仅保存下一个节点得引用。就简单介绍这么点,因为这些是用来帮助我们理解上面代码得含义得。下面我用中文描述一下这个判断干了什么事情,返回 头节点 != 尾节点并且(头节点得下一个节点为空或者头节点得下一个节点得线程不等于当前线程),这么说有一点绕,其实我们看方法名可以知道这个方法是用来判断是否存在等待着得对象想要获得锁,首先假设队列为空,那么头节点等于尾节点,返回false,如果头节点不等于尾节点,那么头节点得下一个节点肯定不为空,然后判断头节点得下一个节点得线程是不是当前线程,如果是,返回false,如果不是,返回true。

然后我们再回归tryAcquire()方法。如果没有等着着得线程。那么它会执行compareAndSetState()方法。这个方法得底层是通过CAS来实现得,这里简单得介绍一下CAS,CAS是一种使用无锁得方式来实现线程安全得方法,这个方法有三个参数,一个是要更新得遍历V,一个是预期值E,一个是新值N,如果V == E,那么更新V为N,如果V != E,那么证明有其他线程更改了这个变量,这个方法不会做任何事情,你可以再重新执行这个方法或者选择放弃。主要流程就是这个,有兴趣得可以去了解一下CAS。如果操作成功,设置当前线程为正在使用得线程,返回true。这里讲解得是c等于0得情况,如果c不等于0呢?判断获得锁得线程是不是当前线程,如果是,c加1,看到这里其实也就明白了重入锁这个词是怎么来的了,它可以一直调用lock方法来加锁,每调用一次,state加1。

然后再回归acquire()方法。如果tryAcquire()方法获取锁成功,那么不会执行其他操作,如果失败,会执行acquireQueued(addWaiter(Node.EXCLUSIVE), args)这个方法,并且当前线程阻塞。那么这个方法又干了什么呢?先来看addWaiter(Node.EXCLUSIVE)这个方法,

static final Node EXCLUSIVE = null;
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;
    }

这个方法判断队列是否为空,如果为空,执行enq()方法,不为空,执行上面得方法,最终得结果都是将当前线程假如到等待队列中。然后acquireQueued()这个方法一层一层得,我没有看太懂,这里也就不说太多,功能其实就是让等待队列前面得获取锁。好了,整个FairSync的lock()方法已经介绍完了,那么NonFairSync的lock()方法有什么区别呢?

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

从代码中可以看到,它不会去管等待队列什么的,而是直接执行CAS操作,如果失败了,好吧,失败了大不了我就用FairSync的那一套咯。

然后我们再来看看unlock()方法。

public void unlock() {
        sync.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()方法,如果成功,并且等待队列不为空的话,唤醒队列中第一个等待的线程。

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

因为重入锁可以多次加锁,因此只有当c为0时,才能返回true。否则,返回false。也就是说,lock几次就要unlock几次才能释放锁。

整个ReentrantLock主要的就介绍完了,这些东西是通过查看源码以及其他的博客整理出来的,整个代码的讲解也是我自己的理解,可能语言组织方面不是太好。希望自己的讲述不是太差劲。。。这篇主要整理了ReetrantLock的原理,下篇博客我准备使用ReentrantLock来实现循环打印数字及字母。

原文地址:https://www.cnblogs.com/xslzjbra/p/9992746.html

时间: 2024-08-26 15:06:07

ReentrantLock重入锁的相关文章

ReentrantLock (重入锁) 源码浅析

一.ReentrantLock简介ReentrantLock重入锁,顾名思义,就是支持重入的锁,它表示能够支持一个线程对资源的重复加锁:我们之前学习过Synchronized锁,它也是支持重入的一种锁,参考我的另一篇Synchronized 锁的实现原理与应用,Synchronized支持隐式的重入锁,比如递归方法,在方法运行时,执行线程在获取到了锁之后仍能连续多次地获取锁:ReentrantLock虽然不能隐式重入,但是获取到锁的线程多次调用lock方法,不会阻塞进入同步队列:除此之外在获取锁

ReentrantLock 重入锁

一.Lock接口: 在Java SE 5之后,并发包中新增了Lock接口及相关实现类来实现锁功能. Lock接口和synchronized关键字实现锁的区别: (1)Lock接口需要显示的获取和释放锁,sychronized是隐式的获取和释放锁.也正因为如此,使得Lock接口拥有了锁获取与释放的可操作性.可中断的获取锁.超时获取锁 等多种synchronized关键字所不具备的同步特性. 例如:一个场景,先获取锁A,然后获取锁B,当B获得后,同时释放A,同时获取锁C.以此类推.这种场景synch

ReentrantLock 重入锁(下)

前沿: ReentrantLock 是java重入锁一种实现,在java中我们通常使用ReentrantLock 和 synchronized来实现锁功能,本篇通过例子来理解下ReentrantLock使用以及什么是可重入锁. 理解可重入: 1. 锁机制是为了多线程并发访问共享资源情况下为保证线程的安全,和对资源的原子性操作, 举个例子: i=i+1;其实是三部操作首先将i读取到线程的临时区存放,然后加一操作,最后将结果写回去.所谓锁机制就是保证一段程序在某段时间只有一个线程执行. 2. 可重入

ReentrantLock(重入锁)简单源码分析

1.ReentrantLock是基于AQS实现的一种重入锁. 2.先介绍下公平锁/非公平锁 公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁.有可能,会造成优先级反转或者饥饿现象. 3.重入锁/不可重入锁 可重入锁:广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁. 不可重入锁

ReentrantLock(重入锁)功能详解和应用演示

1. ReentrantLock简介 jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock.虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景. 2. ReentrantLock和synchronized的相同点 2.1 ReentrantLock是独占锁且可重入的 例子 public class Reentr

[图解Java]ReentrantLock重入锁

图解ReentrantLock 0. demo 我先给出一个demo, 这样大家就可以根据我给的这段代码, 边调试边看源码了. 还是那句话: 注意"My" , 我把ReentrantLock类 改名为了 "MyReentrantLock"类 , "Lock"类 改名为了"MyLock"类. 大家粘贴我的代码的时候, 把相应的"My"都去掉就好了, 否则会编译报错哦. import java.util.Sca

J.U.C重入锁

ReentrantLock重入锁 ReentrantLock是Java并发包中互斥锁,它有公平锁和非公平锁两种实现方式, 重入的意思就是,如果已经获得了锁,如果执行期间还需要获得这个锁的话,会直接获得所,不会被阻塞,获得锁的次数加1:每执行一次unlock,持有锁的次数减1,当为0时释放锁.这点,Synchronized 具有同样语义. Sync在ReentrantLock中有两种子类:非公平锁NonfairSync.公平锁FairSync,默认情况下为非公平锁. 先判断锁的状态,通过CAS来抢

锁 Lock、重入锁、写入锁

ReentrantLock 重入锁 类似于synchronize 区别与写法上,在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定, 不然会造成锁永远无法释放,其他线程永远进不来的结果.eg: 1 package com.zym.height.Lock01; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class Us

【死磕Java并发】-----J.U.C之重入锁:ReentrantLock

此篇博客所有源码均来自JDK 1.8 ReentrantLock,可重入锁,是一种递归无阻塞的同步机制.它可以等同于synchronized的使用,但是ReentrantLock提供了比synchronized更强大.灵活的锁机制,可以减少死锁发生的概率. API介绍如下: 一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大.ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥