java锁源码分析-1获取锁的过程

这篇文章(包括后边两篇)是六月份写的,写完后由于工作较忙,草稿一致没时间修改,今天偶尔翻到,担心哪天弄丢了,于是先以草稿的形式发布,内容有瑕疵,等以后有时间再修复。

解读类的结构

首先来看类的继承关系,ReetrantLock继承的Lock接口,其内部还存在三个内部类,Sync,NonfairSync和FairSync 其中NonfairSync和FairSync都是继承自Sync。

 

这幅图包含了各类中的方法,其中Lock接口有六个方法,ReentrantLock实现了这个接口,

方法从上至下看其名称大概知晓其意思。分别是

  1. lock()加锁
  2. lockInterruptibly()中断等待
  3. tryLock()尝试获取锁
  4. tryLock(long,TimeUnit)在指定时间内尝试获取锁
  5. unlock()释放锁
  6. newCondition()创建一个condition条件

 

看这些方法的实现,全部跟一个private final Sync sync;的成员变量有关。换句话说,lock接口只规定了语义,而真正实现都是使用Sync对象的方法。

 

既然这样我们看Sync类的继承关系。

Sync继承了AbstractQueuedSynchronizer抽象类,AbstractQueuedSynchronizer类又有两个内部类,一个是Node另一个是ConditionObject.

概览一下sync中的方法,lock是个抽象方法留给子类去实现,剩下的也基本见名知意,随意翻开几个方法查看,基本都有调用父类的方法进行实现,因此可以推断出,父类AbstractQueuedSynchronizer可能是非常核心的一个类。

 

Sync有两个子类,NofairSync和FairSync,公平和非公平的同步器,猜想肯定跟公平锁和非公平锁有关。

分析代码

当我们在代码中写到

ReentrantLock lock=new ReentrantLock();实际上是调用了下边这个构造器,里边的同步器是非公平的,这也就是常说的默认情况ReentrantLock使用的是非公平锁。

public ReentrantLock() {

        sync = new NonfairSync();

}

  reteenlock类

  sync类

  AbstractQueuedSynchronizer类

  NonfairSync类

 

看一个常用方法Lock();

public void lock() {

        sync.lock();

}

主要就是调用了sync对象的lock方法,而默认使用的是非公平锁即NoFairSync类的实现

 

final void lock() {

            if (compareAndSetState(0, 1))//比较设置值,设置成功即为获得锁

                setExclusiveOwnerThread(Thread.currentThread());//如果获得锁把当前线程设置为持有线程

            else

                acquire(1);//如果没获取到调用获取方法

        }

尝试获取锁,就是使用乐观思想把值从0设置到1,如果设置成功证明当前线程获取了锁,那么把当前线程设置为持有锁的线程,如果没获取到则执行获取锁的操作

 

protected final boolean compareAndSetState(int expect, int update) {

        // See below for intrinsics setup to support this

        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);

}

这个方法就是原子操作把0设置为1

 

public final void acquire(int arg) {

        if (!tryAcquire(arg) &&//尝试获取锁失败

            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//排队并获取成功,但是线程已经被打断,则调用本线程进行中断,如果线程没有被中断则成功获取锁

            selfInterrupt();//打断获取

}

尝试获取,非公平锁在排队之前还会抢先进行一步获取,如果成功就拿到了锁,如果失败去排队,如果排队也失败,打断当前线程。

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) {//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;//没获取到 返回

        }

 这个方法就是非公平锁抢先尝试获取,如果没有线程占有,直接获取,并设置当前线程为持有线程,如果已经有线程持有,判断是否为自己,如果是自己,自己重入获取锁,把锁的数量加一,如果没获取到返回失败标志。

 

 private Node addWaiter(Node mode) {

        Node node = new Node(Thread.currentThread(), mode);//把当前线程包装成node

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

}

 

代码不复杂也不难,就是包装当前线程为一个node对象,并返回当前node引用

 

 

private Node enq(final Node node) {

        for (;;) {

            Node t = tail;//获取当前尾节点

            if (t == null) { // 如果尾节点为空证明没有排队的

                if (compareAndSetHead(new Node()))//如果新建一个头节点成功

                    tail = head;//把头尾指向同一节点

            } else {//尾节点不为空,证明有排队的

                node.prev = t;//当前节点的前置节点设置为目前的尾节点

                if (compareAndSetTail(t, node)) {//比较设值,设置当前节点为尾节点

                    t.next = node;//设置成功之前尾节点的下一节点设置为当前节点

                    return t;//返回

                }

            }

        }

    }

 

分析这个方法,在一个for循环里并且不走else分支就会始终循环,直到走else分支,

假设进来时tail为null,那么走if这是进行初始化,并设置值,走完if分支之后,tail已经有值,第二次循环走else分支,把当前节点加入到排队队列。重点就是if块是初始化排队队列的关键点。

private final boolean compareAndSetHead(Node update) {

        return unsafe.compareAndSwapObject(this, headOffset, null, update);

    }

 

 headOffset = unsafe.objectFieldOffset

                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));

新建个头节点,headOffset在静态初始化块中记录了head属性的偏移量

 

 

 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)) {//如果前置节点事head节点,那么尝试获取锁

                    setHead(node);//获取成功把当前节点设置为头结点

                    p.next = null; // help GC//断开当前节点前置节点的下一节点指向

                    failed = false;//失败标志设置为false

                    return interrupted;

                }

                if (shouldParkAfterFailedAcquire(p, node) &&//如果前置节点不是head或者获取锁失败,先执行shouldParkAfterFailedAcquire(p, node)方法,如果这个方法返回true执行parkAndCheckInterrupt()方法,否则继续循环

                    parkAndCheckInterrupt())

                    interrupted = true;

            }

        } finally {

            if (failed)//如果等待过程发线程抛出异常,则取消获取

                cancelAcquire(node);//取消获取

        }

}

 

代码中有个无限for循环,只有走if才会结束,开始获取当前节点的前置节点用p指向,如果p是head,尝试获取锁,为什么这么写呢?看图可知,当初始化时sync对象head指向的是一个new出来的没有包装线程的node对象,这个对象是不会去获取锁的,因此这时调用tryAcquire(arg)如果没有其他线程竞争,是可以获取锁的,如果成功获取锁,就将head指向当前节点,同时将前置节点的next设置为null,便于垃圾回收。如果前置节点不是head或者获取锁失败(非公平锁),进入shouldParkAfterFailedAcquire(p, node)方法,如果此方法返回true再执行parkAndCheckInterrupt()方法。

 

static final int CANCELLED =  1;//因为超时或者中断,结点会被设置为取消状态,被取消状态的结点不应该去竞争锁,只能保持取消状态不变,不能转换为其他状态。

static final int SIGNAL    = -1;//表示这个结点的继任结点被阻塞了,到时需要通知它

static final int CONDITION = -2;//表示这个结点在条件队列中,因为等待某个条件而被阻塞

static final int PROPAGATE = -3;//使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取可以无条件传播

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {

        int ws = pred.waitStatus;//获取前置节点的状态

        if (ws == Node.SIGNAL)//如果等于 Node.SIGNAL直接返回true

            return true;

        if (ws > 0) {//如果大于0,循环检查前置节点并提出

            do {

                node.prev = pred = pred.prev;//把当前节点的前置节点指向前置节点的前置节点

            } while (pred.waitStatus > 0);//检查新的前置节点的状态

            pred.next = node;//将最终的新的前置节点的next指向当前节点(彻底断开cancle状态节点的排队等待,剔除它们)

        } else {//等于-2或者-3情况

            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//将前置节点的状态更新为singnal

        }

        return false;

    }

 

分析这个方法,每次进来只能走一个分支,

  1. 假设走第一个分支那么直接返回true就结束。
  2. 假设走ws>0,那么去除前置节点状态为cancle的节点,然后返回false。
  3. 如果上次是去除节点,那么再次进入方法时把新的前置节点状态设置为singnal,然后返回false。然后再次进入这个方法时就回满足第一个条件,返回true。

 

private final boolean parkAndCheckInterrupt() {

        LockSupport.park(this);//阻塞线程

        return Thread.interrupted();//返回线程是否被interrupted

}

 

 

如果期间发生异常则执行cancelAcquire()方法

private void cancelAcquire(Node node) {

        // 不存在直接返回

        if (node == null)

            return;

 

        node.thread = null;//将node对象中的thread置为null

        Node pred = node.prev;//获取当前节点的前置节点

        while (pred.waitStatus > 0)//循环判断前置节点的状态为取消,直到前置节点的状态不再是取消为止

            node.prev = pred = pred.prev;//将当前节点与前置节点断开,并将当前节点的前置节点指向原有前置节点的前置节点

 

  

        Node predNext = pred.next;//获取前置节点的后置节点

        node.waitStatus = Node.CANCELLED;//将当前节点的状态设置为取消

        if (node == tail && compareAndSetTail(node, pred)) {//如果当前节点事尾节点,把当前节点的前置节点设置为尾节点

            compareAndSetNext(pred, predNext, null);

        } else {//如果当前节点不是尾节点,

            int ws;

                            //前置节点不是头节点,并且(满足前置节点的状态为等待,或者满足前置节点的状态小于0并且将前置节点的状态成功的设置为等待状态),并且还要满足前置节点的thread对象不为null

            if (pred != head &&

                ((ws = pred.waitStatus) == Node.SIGNAL ||

                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&

                pred.thread != null) {

                Node next = node.next;//记录当前节点的后置节点

                if (next != null && next.waitStatus <= 0)//后置节点存在且状态不为取消

                    compareAndSetNext(pred, predNext, next);//将前置节点的下一节点设置为当前节点的下一节点,即断开当前节点的连接

            } else {

                unparkSuccessor(node);//唤醒后继等待线程

            }

 

            node.next = node; // help GC

        }

    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值