`
Agrael
  • 浏览: 14997 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类
最新评论

非阻塞算法-ReentrantLock代码剖析之ReentrantLock.lock

阅读更多
ReentrantLock是java.util.concurrent.locks中的一个可重入锁类。在高竞争条件下有更好的性能,且可以中断。深入剖析ReentrantLock的源码有助于我们了解线程调度,锁实现,中断,信号触发等底层机制,实现更好的并发程序。

以下代码出自JDK1.6

先来看ReentrantLock最常用的代码lock
public void lock() {
        sync.lock();
    }

很简单,直接调用了成员变量sync的lock方法。以下是sync的声明
/** Synchronizer providing all implementation mechanics */
    private final Sync sync;


从注释中我们可以看出sync提供了所有的实现机制,ReentrantLock只是简单执行了转发而已。

下图是Sync的层次结构,Sync,FairSync和NonFairSync都是ReentrantLock的静态内部类。Sync 是一个抽象类,而FairSync和NonFairSync则是具体类,分别对应了公平锁和非公平锁。实际中公平锁吞吐量比非公平锁小很多,所以以下讨论若非特别说明均以非公平锁为例。



在上图的类层次中,最核心的类当属 AbstractQueuedSynchronizer ,最重要的两个数据成员当前锁状态和等待链表都是由它来实现的。

/**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;
    /**
     * The synchronization state.
     */
    private volatile int state;


state记录了当前锁被锁定的次数。如果为0则未被锁定。加锁通过更改状态实现,而更改状态主要由函数compareAndSetState实现。调用cas原语以保证操作的原子性,如果state值为expect,则更新为update值且返回true,否则不更改state且返回false.

/**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a <tt>volatile</tt> read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

其中unsafe的类Unsafe为sun的非公开类sun.misc.Unsafe,有兴趣的可以反编译该类查看代码。
而另一个重要的数据当前线程则是在 AbstractOwnableSynchronizer中。

/**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;


了解这些基本的数据结构后让我们来一探上面提到的 sync.lock()之究竟。以下是NonFairSync的lock函数。代码里中文注释均为本文作者添加。

/**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
final void lock() {
            // 如果锁没有被任何线程锁定且加锁成功则设定当前线程为锁的拥有者
            // 如果锁已被当前线程锁定,则在acquire中将状态加1并返回
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 加锁失败,再次尝试加锁,失败则加入等待队列,禁用当前线程,直到被中断或有线程释放锁时被唤醒
                acquire(1);
        }


acquire方法在AbstractQueuedSynchronizer中定义。

/**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
public final void acquire(int arg) {
    // 首先尝试获取锁,成功则直接返回
    // 否则将当前线程加入锁的等待队列并禁用当前线程
    // 直到线程被中断或者在锁为其它线程释放时唤醒
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }


被tryAcquireNonFairSync override,直接调用 Sync.nonfairTryAcquire,代码如下

/**
         * Performs non-fair tryLock.  tryAcquire is
         * implemented in subclasses, but both need nonfair
         * try for trylock method.
         */
        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失败后则进行如下操作

第一步调用AbstractQueuedSynchronizer.addWaiter将当前线程加入等待队列尾部,其中也涉及一些细微的同步操作及逻辑判断,就不详述了。有兴趣者可自行查看源码。

/**
     * Creates and enqueues node for given thread and mode.
     *
     * @param current the thread
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new 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;
    }


第二步调用AbstractQueuedSynchronizer.acquireQueued让线程进入禁用状态,并在每次被唤醒时尝试获取锁,失败则继续禁用线程。

/**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                // 如果当前线程是head的直接后继则尝试获取锁
                // 这里不会和等待队列中其它线程发生竞争,但会和尝试获取锁且尚未进入等待队列的线程发生竞争。这是非公平锁和公平锁的一个重要区别。
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                // 如果不是head直接后继或获取锁失败,则检查是否要禁用当前线程
                // 是则禁用,直到被lock.release唤醒或线程中断
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }


AbstractQueuedSynchronizer. shouldParkAfterFailedAcquire做了一件很重要的事:根据状态对等待队列进行清理,并设置等待信号。

这里需要先说明一下waitStatus,它是AbstractQueuedSynchronizer的静态内部类Node的成员变量,用于记录Node对应的线程等待状态.等待状态在刚进入队列时都是0,如果等待被取消则被设为Node.CANCELLED,若线程释放锁时需要唤醒等待队列里的其它线程则被置为Node.SIGNAL,还有一种状态Node.CONDITION这里先不讨论。

/** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;


AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire实现如下

/**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int s = pred.waitStatus;
        if (s < 0)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park
             */
            // 如果前置结点waitStatus已经被置为SIGNAL,则返回true,可以禁用线程
            return true;
        if (s > 0) {
            // 如果前置结果已被CALCEL,则移除。
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
        do {
        node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    }
        else
            /*
             * Indicate that we need a signal, but don't park yet. Caller
             * will need to retry to make sure it cannot acquire before
             * parking.
             */
            // 原子性将前置结点waitStatus设为SIGNAL
            compareAndSetWaitStatus(pred, 0, Node.SIGNAL);
        // 这里一定要返回false,有可能前置结点这时已经释放了锁,但因其 waitStatus在释放锁时还未被置为SIGNAL而未触发唤醒等待线程操作,因此必须通过return false来重新尝试一次获取锁
        return false;
    }


AbstractQueuedSynchronizer.parkAndCheckInterrupt实现如下,很简单,直接禁用线程,并等待被唤醒或中断发生。对java中Thread.interrupted()都作了什么不甚了解的要做功课。

这里线程即被堵塞,醒来时会重试获取锁,失败则继续堵塞。即使Thread.interrupted()也无法中断。那些想在等待时间过长时中断退出的线程可以调用ReentrantLoc.lockInterruptibly().其原理后继文章中会剖析。 

/**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }


关于线程的更多知识可以参考:http://www.ibm.com/developerworks/cn/java/j-concurrent/

该文章为Agrael转帖,并在转帖的内容上做了一定的更改和注释,版权属于原作者拥有。红色字体为Agarel添加的注释。
  • 大小: 26.2 KB
分享到:
评论
4 楼 dennis_zane 2010-09-07  
转贴怎么不在开头注明出处?
3 楼 only_xxp 2010-09-06  
学习!!!
2 楼 Agrael 2010-03-24  
mercyblitz 写道
这个东西不难理解,关键是JVM的在不同硬件平台的实现,看起来头痛。

JVM的确很值得研究,不单是这一快,里面的很多思想都是值得我们学习的地方。
1 楼 mercyblitz 2010-03-24  
这个东西不难理解,关键是JVM的在不同硬件平台的实现,看起来头痛。

相关推荐

    Lock、Synchoronized和ReentrantLock的使用

    Lock、Synchronized 和 ReentrantLock 的使用 Lock、Synchronized 和 ReentrantLock 是 Java 中三种常用的同步机制,每种机制都有其特点和使用场景。下面对这三种机制进行详细的分析和比较。 一、Synchronized ...

    实战Java高并发程序设计第二版随书代码

    - **线程同步**:为了防止多个线程同时访问共享资源导致的数据不一致,Java提供了synchronized关键字、 volatile变量、Lock接口(如ReentrantLock)等同步机制。 2. **并发控制** - **volatile**:了解volatile...

    java核心知识点整理.pdf

    可达性分析............................................................................................................................................... 26 2.3.2. 2.3.3. 老年代 ........................

    JAVA核心知识点整理(有效)

    可达性分析............................................................................................................................................... 26 2.3.2. 2.3.3. 老年代 ........................

    java面试——深圳-乐信-Java高级.zip

    - 线程同步机制:synchronized关键字,volatile关键字,Lock接口(如ReentrantLock)。 - 死锁、活锁、饥饿现象及避免策略。 - 线程通信:wait()、notify()、notifyAll()方法,生产者消费者模型。 - ...

    java面试——深圳-商汤科技-Java高级.zip

    了解线程同步机制,如synchronized关键字、wait/notify机制、Lock锁、ReentrantLock等,以及并发工具类如ExecutorService、Semaphore、CyclicBarrier等,都是面试中的常见问题。 3. **内存管理与垃圾收集**:Java的...

    互联网大厂Java高级工程师岗位面试真题154道

    - ReentrantLock提供了tryLock方法,可以尝试获取锁而不阻塞。 - ReentrantLock支持更细粒度的锁,如读写锁(ReadWriteLock)。 Java并发工具还包括Semaphore(信号量)、CountDownLatch(倒计时器)、...

    Java面试宝典--牛客网.zip

    3. NIO:理解非阻塞I/O模型,包括通道、缓冲区和选择器的使用。 五、反射 1. 类的加载:理解类加载的过程,以及Class对象的获取方式。 2. 反射API:熟练使用Class、Constructor、Method、Field等类进行动态操作对象...

    java代码优化总结1.0版本.7z

    使用synchronized关键字时,尽量缩小锁的粒度,考虑使用Lock接口提供的高级同步机制,如ReentrantLock。另外,合理利用并发容器,如ConcurrentHashMap,它们内部设计有优化的并发控制,比简单的同步集合更高效。 除...

    Java多线程-避免同步机制带来的死锁问题及用Lock锁解决线程安全问题

    `Lock` 接口是 JDK 5.0 引入的新特性之一,用于实现更高级别的锁机制。相比传统的 `synchronized` 关键字,`Lock` 提供了更多灵活的控制能力。 **主要特点**: - **可重入性**:支持可重入锁定,允许多次锁定同一个...

    java面试——上海-携程-Java高级.zip

    - 线程同步:synchronized关键字、volatile变量、Lock接口(如ReentrantLock)以及Condition。 - 死锁、活锁和饥饿现象的识别与避免。 - 守护线程(Daemon Thread)的使用和理解。 4. **并发编程** - 并发工具...

    (2024)跳槽涨薪必备精选面试题.pdf

    4. **ReentrantLock中tryLock()和lock()方法的区别** - `tryLock()` 尝试获取锁,可设置等待时间,不会阻塞线程。 - `lock()` 必须获取到锁才会返回,否则一直阻塞直到获取到锁。 5. **CountDownLatch和Semaphore...

    java面试知识点整理.zip

    - Lock接口及其实现类,如ReentrantLock、ReadWriteLock。 2. **Spring框架**: - Spring IoC(控制反转)和DI(依赖注入)原理。 - AOP(面向切面编程)及其在Spring中的实现。 - Spring Bean的生命周期管理。...

    Java后端常见面试题.zip

    - **同步机制**:synchronized、volatile、Lock接口及其子类如ReentrantLock的应用。 - **线程池**:ExecutorService、ThreadPoolExecutor、FutureTask等的使用和配置优化。 4. **并发编程** - **并发容器**:...

    2016最新Java面试题详细版.zip_java面试

    - 熟悉线程同步机制,如synchronized、volatile、Lock接口及ReentrantLock、ReentrantReadWriteLock等。 - 理解线程池的工作原理,如ThreadPoolExecutor和Executors的使用。 5. **IO与NIO** - 了解传统IO流...

    一个简单的Java并发系统动态测试工具.zip

    Java提供了多种并发机制,包括线程(Thread)、Future、ExecutorService、Callable接口等,以及同步机制如synchronized关键字、wait/notify、Lock接口(如ReentrantLock)等。 2. **线程与线程池** 在Java中,线程...

    java 飞流直下的代码

    3. **并发编程**:在多线程环境中,理解synchronized关键字、volatile变量、ThreadLocal以及Lock接口(如ReentrantLock)的使用,能帮助我们编写出更加高效的并发代码。 4. **编译器优化**:Java的Just-In-Time ...

    Java各公司面试题

    2. 垃圾收集:了解垃圾收集机制,包括GC算法、对象的可达性分析,以及如何调整垃圾收集器参数。 四、集合框架 1. 集合接口:熟悉List、Set、Queue等主要集合接口的特性和应用场景。 2. 实现类:掌握ArrayList、...

    Java面试宝典(高级篇).docx

    2. **多线程**:掌握线程的创建方式(Thread类和Runnable接口),理解线程同步机制(synchronized关键字,wait()和notify()方法,Lock接口和ReentrantLock类),以及死锁和活锁的概念。 3. **JVM内存模型**:了解堆...

    汪文君高并发编程实战视频资源下载.txt

    │ 高并发编程第一阶段04讲、线程生命周期以及start方法源码剖析.mp4 │ 高并发编程第一阶段05讲、采用多线程方式模拟银行排队叫号.mp4 │ 高并发编程第一阶段06讲、用Runnable接口将线程的逻辑执行单元从控制中...

Global site tag (gtag.js) - Google Analytics