`
singleant
  • 浏览: 378626 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

【java并发】juc高级锁机制探讨

阅读更多

 

 

最近在看一些juc相关的设计和源码,接上文 【java并发】基于JUC CAS原理,自己实现简单独占锁

本文探讨一下juc里面提供的一些高级锁机制和基本原理。

 

JUC高级锁机制简介

Juc 提供了高级锁的一些特性和应用,如:

ReentrantLock :和 synchronized 具有差不多的语义,独占锁,同时只有一个线程能获得锁。

ReentrantReadWriteLock :读写锁。读允许共享,写独占。适用于读频繁的场景。

CountDownLatch : 闭锁。使用场景类似比赛鸣枪,在没有鸣枪之前所有的运动员 ( 线程 ) 都必须等待。说白了就是使用于一个或多个线程等待某一个条件成立了,触发运行。

Semaphore :信号量。使用场景类似通行证,通行证数量有限,拿到证的才能通行。

CyclicBarrier : 周期障碍。语义上和 CountDownLatch 有点类似,只有几个线程打到一个共同的状态之后,触发后续动作继续。不同在于 1. 达到共同状态后,可以指定一个后续触发的线程对象。 2. 周期性意味着可以周期运行。

FutureTask :带有返回值的异步执行。

至于各种锁机制使用场景这里不赘述,后面还有机会加一些例子。

以上不同的锁机制和使用场景,不管我们我们叫锁、闭锁、信号量等等。抽象之后都有一种共同的语义:

多线程并发的执行,之间通过某种 共享 状态来同步,只有当状态满足 xxxx 条件,才能触发线程执行 xxxx

这个共同的语义可以称之为同步器。可以认为以上所有的锁机制都可以基于同步器定制来实现的。

如果要实现一个特定场景的锁来同步线程的执行,其实并不难,如上文 【java并发】基于JUC CAS原理,自己实现简单独占锁 而juc里的思想是 将这些场景抽象出来的语义通过统一的同步框架来支持。

juc 里所有的这些锁机制都是基于 AQS AbstractQueuedSynchronizer )框架上构建的。下面简单介绍下 AQS AbstractQueuedSynchronizer )。 可以参考Doug Lea的论文The java.util.concurrent Synchronizer Framework

 

AQS框架 

AbstractQueuedSynchronizer 是一个抽象类,里面定义了同步器的基本框架,实现了基本的结构功能。只留有状态条件的维护由具体同步器根据具体场景来定制,如上面提到的 ReentrantLock RetrantReadWriteLock和CountDownLatch 等等。

一个同步器至少需要包含两个功能:

1.       获取同步状态

如果允许,则获取锁,如果不允许就阻塞线程,直到同步状态允许获取。

2.       释放同步状态

修改同步状态,并且唤醒等待线程。

根据作者论文, aqs 同步机制同时考虑了如下需求:

1.       独占锁和共享锁两种机制。

2.       线程阻塞后,如果需要取消,需要支持中断。

3.       线程阻塞后,如果有超时要求,应该支持超时后中断的机制。

 

实现涉及基本技术原理  

1.       状态位   

提供 volatile 变量 state;  用于同步线程之间的共享状态。通过 CAS volatile 保证其原子性和可见性。对应源码里的定义:

    /**
     * 同步状态
     */
    private volatile int state;

    /**
     *cas
     */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
 

2.       线程阻塞和唤醒

有别于wait和notiry。这里利用 jdk1.5 开始提供的 LockSupport.park() LockSupport.unpark() 的本地方法实现,实现线程的阻塞和唤醒。

3.       阻塞线程节点队列 CHL Node queue

根据论文里描述, AQS 里将阻塞线程封装到一个内部类 Node 里。并维护一个 CHL Node FIFO 队列。 CHL 队列是一个非阻塞的 FIFO 队列,也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性。实现无锁且快速的插入。关于非阻塞算法可以参考  Java 理论与实践: 非阻塞算法简介 。CHL队列对应代码如下:

 

     /**
     * CHL头节点
     */ 
   private transient volatile Node head;
    /**
     * CHL尾节点
     */
    private transient volatile Node tail;

  Node节点是对Thread的一个封装,结构大概如下:

    static final class Node {
        /** 代表线程已经被取消*/
        static final int CANCELLED =  1;
        /** 代表后续节点需要唤醒 */
        static final int SIGNAL    = -1;
        /** 代表线程在等待某一条件/
        static final int CONDITION = -2;
        /** 标记是共享模式*/
        static final Node SHARED = new Node();
        /** 标记是独占模式*/
        static final Node EXCLUSIVE = null;

        /**
         * 状态位 ,分别可以使CANCELLED、SINGNAL、CONDITION、0
         */
        volatile int waitStatus;

        /**
         * 前置节点
         */
        volatile Node prev;

        /**
         * 后续节点
         */
        volatile Node next;

        /**
         * 节点代表的线程
         */
        volatile Thread thread;

        /**
         *连接到等待condition的下一个节点
         */
        Node nextWaiter;

    }
 

 

 

AQS 源码

 

AQS实现了一个同步器的基本结构,下面以独占锁和非独占锁区分来看看 AQS 的几个主要方法:

独占模式

独占获取: tryAcquire 本身不会阻塞线程,如果返回 true 成功就继续,如果返回 false 那么就阻塞线程并加入阻塞队列。

 

    public final void acquire(int arg) {

        if (!tryAcquire(arg) &&

            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//获取失败,则加入等待队列

            selfInterrupt();

} 
 


 

独占且可中断模式获取:支持中断取消

public final void acquireInterruptibly(int arg) throws InterruptedException {

        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))

            doAcquireInterruptibly(arg);

    } 
 

独占且支持超时模式获取: 带有超时时间,如果经过超时时间则会退出。

 

    public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {

         if (Thread.interrupted())

             throw new InterruptedException();

         return tryAcquire(arg) ||

             doAcquireNanos(arg, nanosTimeout);

}
 

 

独占模式释放:释放成功会唤醒后续节点

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

共享模式

 

共享模式获取

 

    public final void acquireShared(int arg) {

        if (tryAcquireShared(arg) < 0)

            doAcquireShared(arg);

}

 

  可中断模式共享获取

  

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    } 

  

共享模式带定时获取

 

    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
         if (Thread.interrupted())
             throw new InterruptedException();
         return tryAcquireShared(arg) >= 0 ||
             doAcquireSharedNanos(arg, nanosTimeout);
    } 

 

共享锁释放

  

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    } 
 

 

 

注意以上框架只定义了一个同步器的基本结构框架,的基本方法里依赖的 tryAcquire tryRelease tryAcquireShared tryReleaseShared 四个方法在 AQS 里没有实现,这四个方法不会涉及线程阻塞,而是由各自不同的使用场景根据情况来定制:

 

 

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

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

 

从以上源码可以看出AQS实现基本的功能:

AQS虽然实现了acquire,和release方法是可能阻塞的,但是里面调用的tryAcquire和tryRelease是由子类来定制的且是不阻塞的可。以认为同步状态的维护、获取、释放动作是由子类实现的功能,而动作成功与否的后续行为时有AQS框架来实现。所以可以认为同步器实现了一下功能:

1.同步器基本范式、结构

2.状态获取、释放成功或失败的后续行为,如线程的阻塞、唤醒机制

3.线程阻塞队列的维护

 

状态获取、释放动作本身是由子类来定义的。

 

 

还有以下一些私有方法,用于辅助完成以上的功能:

final boolean acquireQueued(final Node node, int arg) :申请队列

private Node enq(final Node node) : 入队

private Node addWaiter(Node mode) :以mode创建创建节点,并加入到队列

private void unparkSuccessor(Node node) 唤醒节点的后续节点,如果存在的话。

private void doReleaseShared() :释放共享锁

private void setHeadAndPropagate(Node node, int propagate):设置头,并且如果是共享模式且propagate大于0,则唤醒后续节点。

private void cancelAcquire(Node node) :取消正在获取的节点

private static void selfInterrupt() :自我中断

private final boolean parkAndCheckInterrupt() park 并判断线程是否中断

 

从源码可以看出AQS实现基本的功能:

1.同步器基本范式、结构

2.线程的阻塞、唤醒机制

3.线程阻塞队列的维护

 

AQS虽然实现了acquire,和release方法,但是里面调用的tryAcquire和tryRelease是由子类来定制的。可以认为同步状态的维护、获取、释放动作是由子类实现的功能,而动作成功与否的后续行为时有AQS框架来实现。

 

ReentrantLock原理

有了AQS基础,下面来看ReentrantLock的基本原理:

ReentrantLock原理

由于同步器里已经定义了基本的结构,包括获取、释放、和阻塞队列维护和管理等。ReentrantLock是一个独占互斥锁,里只需要实现TryAcquire、TryRelease等方法,告诉同步器是否获取和释放状态成功。其他的后续行为都由AQS框架完成。由于ReentrantLock是一个可重入的独占锁,所以同步器状态可以直接根据是否==0来判断是否可用。

ReentrantLock主要提供lock和unlcok两个方法。

而lock和unlock正是基于AQS的一个子类同步器来实现。里面sync同步器有两种实现,一种是公平锁,一种是非公平锁。默认是非公平锁,看看非公平锁实现tryAcquire

           final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {//如果状态位为0,那么尝试获取
                if (compareAndSetState(0, acquires)) {//基于CAS获取和修改状态
                    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

 

 

            protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (isFirst(current) &&
                    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;
        }

 可以看看 非公平锁的不会根据FIFO,而公平锁会判断是否是第一个线程,根据FIFO来执行。

 

 

 

 

参考文献:

The java.util.concurrent Synchronizer Framework

  Java 理论与实践: 非阻塞算法简介

《深入浅出 Java Concurrency》目录

2
1
分享到:
评论
1 楼 demoxshiroki 2015-04-03  
AQS是不是少讲了 CondtionObject

相关推荐

    JUC线程锁框架

    Java并发编程是Java平台的重要特性,它为多线程编程提供了强大的支持。JUC,全称为Java Util Concurrency,是Java并发包的简称,包含了大量用于处理并发问题的类和接口,极大地简化了多线程环境下的编程。在这个深度...

    JUC代码收集,java高并发多线程学习

    Java并发编程是Java开发中的重要领域,而Java.util.concurrent(JUC)工具包则是Java并发编程的核心组件。这个集合提供了一系列高效、线程安全的类和接口,用于简化多线程环境下的编程任务。本资源"JUC代码收集,...

    深入浅出_Java并发工具包原理讲解

    Java并发工具包(J.U.C)是Java编程语言中用于并发编程的一系列工具包的统称,它包含了一系列方便实现多线程编程的类和接口,使得开发者可以更加方便地编写高效、线程安全的程序。本文将深入浅出地探讨J.U.C的原理和...

    java之JUC系列.pdf

    需要注意的是,由于内容里存在OCR扫描识别错误,以上知识点是基于对可识别内容的理解整理而成,可能并不完全准确,但提供了JUC中关键组件的功能及其应用场景,方便读者了解和掌握Java并发编程的高级特性。

    龙果学院java并发编程完整视频

    Java并发编程是Java语言中的一个高级特性,它允许程序在多线程环境中运行,从而有效地利用多核处理器的能力,提高程序的执行效率。并发编程涉及到线程管理、同步机制、共享资源访问控制等多个方面,是现代软件开发中...

    JUC并发编程与源码分析视频课.zip

    《JUC并发编程与源码分析视频课》是一门深入探讨Java并发编程的课程,主要聚焦于Java Util Concurrency(JUC)库的使用和源码解析。JUC是Java平台提供的一组高级并发工具包,它极大地简化了多线程编程,并提供了更...

    Java-并发(Concurrent)编程

    5. **Java并发工具包(JUC)**:包括原子类、并发容器(如`ConcurrentHashMap`)、阻塞队列(如`ArrayBlockingQueue`)以及锁机制。 6. **Lock和AQS**:Lock接口提供了比`synchronized`更灵活的锁机制,而...

    java并发编程实践

    关于锁,Java并发编程实践中提到了synchronized关键字,它是一种内置锁,用于实现对对象的同步访问,防止多个线程同时操作同一个资源。Java并发包中也提供了其他锁的实现,比如ReentrantLock,它提供了更多灵活的...

    Java并发编程实践

    在探讨Java并发编程实践时,首先需要理解进程和线程这两个基本概念,以及它们在计算机科学中的作用和区别。在单道程序设计环境下,程序在运行时独占系统资源,并且具有封闭性和可再现性。封闭性意味着程序一旦开始...

    JAVA并发编程实践(中文)

    《JAVA并发编程实践》是一本深入探讨Java平台上的多线程和并发编程的权威书籍。这本书旨在帮助开发者理解和掌握在Java环境中如何有效地编写并发代码,从而利用多核处理器的优势,提高程序性能。以下是对该书内容的...

    尚硅谷JUC百度云连接

    接下来,我们将围绕这个主题进行深入探讨,包括Java并发编程的基础概念、核心组件以及尚硅谷提供的学习资源可能涵盖的内容等方面。 ### Java并发编程基础 Java并发编程是指在Java中实现多线程并行处理的技术。随着...

    深入分析Java并发编程之CAS

    本文将深入探讨Java并发编程中的CAS机制及其在Java中的实现。 首先,CAS操作的原理是:在执行更新操作前,先比较当前变量的值是否与预期值相等,如果相等则更新,否则不做任何操作。这种乐观锁策略假设并发冲突较少...

    juc源码视频教程最全

    Java并发编程是现代Java开发中的重要组成部分,Java并发 utilities(JUC)库是Java平台标准版(Java SE)的一部分,提供了强大的工具和类库来帮助开发者编写高效的多线程和并发程序。本教程将深入探讨JUC源码,旨在...

    Java并发编程实践中(中+英+例子源码)

    这本书“Java并发编程实践中(中+英+例子源码)”提供了深入理解Java并发机制的宝贵资源。下面我们将详细探讨其中涉及的一些关键知识点。 1. **线程与进程**:在并发编程中,线程是程序执行的基本单位,而进程则是...

    面试必问并发编程高级面试专题.zip

    并发编程是计算机科学中的一个重要领域,特别是在Java等多线程...以上内容涵盖了并发编程中的核心概念和Java并发编程的关键知识点,对于准备高级面试的IT专业人士来说,理解并掌握这些内容将有助于在面试中表现出色。

    Java concurrency之锁_动力节点Java学院

    本文将深入探讨Java中的锁机制,主要包括同步锁(synchronized)和Java并发工具包(Java Concurrency Utilities, JUC)中的锁。 同步锁是Java 1.0版本就引入的机制,主要通过`synchronized`关键字实现。它确保对...

    JUC多线程及高并发1

    Java并发编程是提升系统性能和资源利用率的关键技术之一,JUC(Java Concurrency Utilities)是Java平台中的并发工具包,提供了丰富的并发控制和同步机制。本文将深入探讨JUC中的多线程及高并发相关知识点。 一、...

    JUC学习笔记(Java多线程)

    总的来说,JUC学习笔记涵盖了Java多线程编程的主要方面,包括线程的创建与管理、同步机制、并发容器以及协调工具的使用,这些都是提升Java并发编程能力的关键知识点。通过深入理解和熟练应用这些工具,开发者可以更...

    java八股文之JUC.zip

    Java并发编程是Java开发中的重要领域,特别是在设计高并发、高性能的应用系统时,对Java Concurrency Utility (JUC) 工具包的理解和运用显得尤为关键。本篇将深入探讨Java八股文中涉及的JUC相关知识点,帮助你在面试...

    juc入门案例演示代码

    Java并发编程是Java程序员需要掌握的重要技能之一,Java并发库(Java Util Concurrency, JUC)为多线程编程提供了强大的支持。在这个"juc入门案例演示代码"中,我们将会探讨两个关键的JUC组件:`JUCLock`和`process_...

Global site tag (gtag.js) - Google Analytics