`

ReentrantLock原理的源码解读

 
阅读更多
可以参照http://blog.csdn.net/chen77716/article/details/6641477.本文的很多思想都来源于这个文章.这文章说的真的是很透.

我们主要关注lock和unlock方法


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


直接调用的是sync的lock.


  public ReentrantLock() {
        sync = new NonfairSync();
    }


默认是非公平锁,看看NonfairSync的lock方法:

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


compareAndSetState(0, 1) 这个是尝试获取锁,把state的状态从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);
    }


unsafe的compareAndSwapInt方法是native的.
但是我们更关注的是,申请锁不成功的时候是怎么做的.可以看到是acquire(1);


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


acquire首先调用的是tryAcquire.看看NonfairSync的tryAcquire是怎么样实现的:

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


tryAcquire的逻辑是这样的,
c = getState() 就是当前没有锁竞争的时候,会再尝试去获得锁.
current == getExclusiveOwnerThread()):当前线程已经获取锁了,那么锁的记数加一

如果tryAcquire没有成功,  就执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 
addWaiter是把线程和线程的状态信息封装到一个node对象,node是个链表.
其实就是把当前线程放到一个链表的末尾去.具体怎么放有点讲究,而且用到了无限循环,也就是说,一定要把线程放进链表的.


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


compareAndSetTail 这个方法就是尝试把当前线程放到一个链表的末尾去.
如果没有成功,执行enq(node);


private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                Node h = new Node(); // Dummy header
                h.next = node;
                node.prev = h;
                if (compareAndSetHead(h)) {
                    tail = node;
                    return h;
                }
            }
            else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }


for (;;)  这里用到了无限循环.
在addWaiter里,放进链表的条件是链表的结尾元素不能为null,在enq方法发现这种情况会创建一个node对象取代之前的链表的结尾元素.

现在来看acquireQueued方法:


final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }

acquireQueued也是个无限循环.就是说要么获取到锁,要么中断当前线程.
acquireQueued会再次调用tryAcquire,就是再尝试一次获取锁.
shouldParkAfterFailedAcquire是判断是否要中断当前线程.


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
	    do {
		node.prev = pred = pred.prev;
	    } while (pred.waitStatus > 0);
	    pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        } 
        return false;
    }


shouldParkAfterFailedAcquire返回true的话,当前线程就直接中断了
返回false的话,会无限循环再来一次,期间会删除掉废弃的node(pred.waitStatus > 0)
会一直尝试把前面的节点的waitStatus设置为SIGNAL,这个其实就是返回true的条件.
也就是说会不断尝试直到返回true,然后中断当前线程.

线程这个时候还没获取锁,但是已经被中断了,这个时候只有等待被唤醒,然后再尝试去或得锁.
这个逻辑是可以在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;
    }


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

从tryRelease可以看到释放锁的条件是:c == 0 就是锁的计数为0;
unparkSuccessor:释放锁.


 private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        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;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }


LockSupport.unpark(s.thread);  可以看到释放锁的时候会唤醒一个之前链表的一个线程,这样线程的加锁和解锁就串起来了.  这里最终调用的是unsafe.unpark.

线程等待最终调用的是unsafe.park.
线程唤醒最终调用的是unsafe.park.
可以看到unsafe才是最终的实现,也可以看到unsafe的方法都是native的.








0
2
分享到:
评论

相关推荐

    系统解析JDK源码,领略大牛设计思想,JAVA进阶必备(2023新课,已完结)

    8. **多线程同步**:synchronized关键字、Lock接口及其实现如ReentrantLock,以及并发工具类如Atomic系列,源码解读能揭示其内部同步机制,提升多线程编程能力。 9. **字符串处理**:String类的不可变性、...

    带你看看Java-AQS同步器 源码解读四 条件队列Condition上

    Java AQS(AbstractQueuedSynchronizer)是一个强大的同步组件,它被广泛应用于并发编程中,如Java的Lock...通过对AQS源码的深入解读,我们可以更好地理解并发编程的底层机制,从而编写出更加高效和健壮的多线程代码。

    JAVA源码JAVA网络通信系统的研究与开发(论文+源代码+开题报告)

    根据提供的文件信息,本文将对“JAVA源码JAVA网络通信系统的研究与开发(论文+源代码+开题报告)”中的关键技术点进行详细解读,并结合相关内容分析该系统的实现原理及应用场景。 ### 一、项目背景 随着互联网技术的...

    Android面试复习资料大全(包含java源码)

    10. **Java并发编程**:包括创建线程的三种方式、线程同步机制Synchronized和ReentrantLock、线程池、死锁、volatile关键字和CAS原子操作。 11. **Java并发集合**:ArrayBlockingQueue、LinkedBlockingQueue、...

    JDK自带多线程工具包详解

    由浅入深,通过图解和手写代码,讲解Java版...AQS原理&源码解读 线程同步+各种锁的原理&手写实现 JDK多线程工具包中,若干种工具的原理和手写实现: ReentrantLock、CountDownLanuh、CyclicBarrier、Semaphore      

    java学习

    综上所述,这个“Java学习”主题涵盖了线程这一核心概念,涉及线程的创建、管理和同步,可能还包括了对Java源码的解读以及开发工具的使用。通过深入学习这部分内容,开发者能够提升自己在并发编程领域的技能,更好地...

    JDK library usage analyze.

    标签“源码”提示我们分析可能会涉及JDK的源代码阅读,通过查看原始实现来理解其工作原理,这对于解决复杂问题或优化性能是至关重要的。而“工具”可能意味着会介绍一些辅助分析的工具,如代码分析工具、性能监控...

    java面试宝典

    从提供的文件内容中,我们可以得知这本《java面试宝典》旨在帮助求职者全面准备Java开发相关的技术面试,其中不仅包含了大量关于Java核心知识点的解析,还涉及了并发编程、JVM原理以及源码层面的深度理解,非常适合...

    java综合面试题

    以下是对这些知识点的详细解读: 1. **基础语法**:面试中常常会考察对Java基础语法的掌握,如数据类型、变量、运算符、流程控制语句(if、switch、for、while)、方法、类与对象等。此外,封装、继承和多态作为...

    Java装逼指南.pdf

    以下是对该文档中提到的一些核心概念和知识点的深入解读。 ### 第一部分:基本语法 #### 1. 关键字 - **static**:用于定义静态变量、方法或内部类。静态成员属于类本身,而不是类的实例。 - **final**:用于声明...

Global site tag (gtag.js) - Google Analytics