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

AVA并发编程学习笔记之AQS简介

 
阅读更多
AVA并发编程学习笔记之AQS简介
分类: JAVA并发编程 2012-05-10 21:19 815人阅读 评论(1) 收藏 举报
java编程thread框架semaphorejvm
目录(?)[+]
1、引言
JAVA内置的锁(使用同步方法和同步块)一直以来备受关注,其优势是可以花最小的空间开销创建锁(因为每个JAVA对象或者类都可以作为锁使用)和最少的时间开销获得锁(单线程可以在最短时间内获得锁)。线程同步越来越多地被用在多处理器上,特别是在高并发的情况下,然而,JVM内置锁表现一般,而且不支持任何公平策略。从JAVA 5开始在java.util.concurrent包中引入了有别于Synchronized的同步框架。
下面谈谈它的设计思路:
设计一个同步器至少应该具以下有两种操作:一个获取方法,如果当前状态不允许,将一直阻塞这个线程;一个释放方法,修改状态,让其他线程有运行的机会。并发包中并没有为同步器提供一个统一的API,获取和释放方法在不同的类中的名称不同,比如获取方法有:Lock.lock,Semaphore.acquire, CountDownLatch.await和FutureTask.get.这些方法一般都重载有多种版本:阻塞与非阻塞版本、支持超时、支持中断。
java.util.concurrent包中有很多同步类,比如互斥锁、读写锁、信号量等,这些同步类几乎都可以用不同方式来实现,但是如果这样做,那么这样的项目充其量只能算一个二流工程。JSR166并没有生搬硬套,而是建立了一个同步中心类AbstractQueuedSynchronizer(简称:AQS)的框架,其中提供了大量的同步操作,而且用户还可以在此类的基础上自定义自己的同步类。其设计目标主要有两点:
1、提高可扩展性,用户可以自定义自己的同步类
2、最大限度地提高吞吐量,提供自定义公平策略

2、设计和实现
同步器的设计比较直接,前面提到包含获取和释放两个操作:
获取操作过程如下:
while (synchronization state does not allow acquire) {
    enqueue current thread if not already queued;
    possibly block current thread;
}
dequeue current thread if it was queued;
释放操作:
update synchronization state;
if (state may permit a blocked thread to acquire)
    unblock one or more queued threads;
要满足以上两个操作,需要以下3点来支持:
1、原子操作同步状态;
2、阻塞或者唤醒一个线程;
3、内部应该维护一个队列。

2.1同步状态
AQS用的是一个32位的整型来表示同步状态的,可以通过以下几个方法来设置和修改这个状态字段:getState(),setState(),compareAndSetState().这些方法都需要java.util.concurrent.atomic包的支持,采用CAS操作.将state设置为32位整型是一个务实的决定,虽然JSR166提供了64位版本的原子操作,但它还是使用对象内部锁来实现的,如果采用64位的state会导致同步器表现不良好。32位同步器满足大部分应用,如果确实需要64位的状态,可以使用AbstractQueuedLongSynchronizer类.AQS是一个抽象类,如果它的实现类想要想要拥有对获取和释放的控制权,那它必须实现tryAcquire和tryRelease两个方法。
[html] view plaincopyprint?
protected final int getState() { 
    return state; 

 
 
protected final void setState(int newState) { 
    state = newState; 

 
 
protected final boolean compareAndSetState(int expect, int update) { 
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update); 

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

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


2.2阻塞
JSR166以前还没有好的阻塞和解除阻塞线程的API可以使用!只有Thread.suspend 和 Thread.resume,但这两个方法已经被废弃了,原因是有可能导致死锁。如果一个线程拥有监视器然后调用 Thread.suspend 使自已阻塞,另一个线程试图调用Thread.resume去唤醒它,那么这个线程去获取监视器时即出现死锁。直到后来出现的LockSupport解决了这个问题,LockSupport.park可以阻塞一个线程,LockSupport.unpack可以解除阻塞,调用一次park,然后调用多次unpack只会唤醒一个线程,阻塞针对线程而不是针对同步器。特别的,如果一个线程在一个新的同步器上调用pack方法有可能立即返回,因为可能有剩余的unpack存在。虽然调用多次unpack是想彻底清除阻塞状态,但这显得很笨拙,而且不划算,更有效的做法是在多次park的时候才多次unpark.

2.3队列
同步框架最重要的是要有一个同步队列,在这里被严格限制为FIFO队列,因此这个同步框架不支持基于优先级的同步策略。同步队列采用非阻塞队列毋庸置疑,当时非阻塞队列只有两个可供选择CLH队列锁和MCS队列锁.原始的CLH Lock仅仅使用自旋锁,但是相对于MSC Lock它更容易处理cancel和timeout,所以选择了CLH Lock。
CLH队列锁的优点是:进出队快,无锁,畅通无阻(即使在有竞争的情况下,总有一个线程总是能够很快插入到队尾);检查是否有线程在等待也是很容易的(只需要检查头尾指针是否相同)。最后设计出来的变种CLH Lock和原始的CLH Lock有较大的差别:
1、为了可以处理timeout和cancel操作,每个node维护一个指向前驱的指针。如果一个node的前驱被cancel,这个node可以前向移动使用前驱的状态字段。
2、第二个变动是在每个node里使用一个状态字段去控制阻塞,而不是自旋。一个排队的线程调用acquire,只有在通过了子类实现的tryAcquire才能返回,确保只有队头线程才允许调用tryAcquire。
3、另外还有一些微小的改动:head结点使用的是傀儡结点。
变种的CLH队列如下图所示:


2.4条件队列
同步框架提供了一个ConditionObject,一般和Lock接口配合来支持互斥模型,它提供类似JVM同步器的操作。条件对象可以和其他同步器有效的整合,它修复了JVM内置同步器的不足:一个锁可以有多个条件。条件结点内部也有一个状态字段,条件结点是通过nextWaiter指针串起来的一个独立的队列。条件队列中的线程在获取锁之前,必须先被transfer到同步队列中去。transfer先断开条件队列的第一个结点,然后插入到同步队列中,这个新插入到同步队列中的结点和同步队列中的结点一起排队等待获取锁。

3、用法
AbstractQueuedSynchronizer是一个采用模板方法模式实现的同步器基类,子类只需要实现获取和释放方法。子类一般不直接用于同步控制,而是采用代理模式。因为获取和释放方法一般是私有的,实现细节不必暴露出来,所以常用委派的方法来使用同步器类:在一个类的内部申请一个私有的AQS的子类,委派它的所有同步方法。
[java] view plaincopyprint?
class Mutex { 
    class Sync extends AbstractQueuedSynchronizer { 
        public boolean tryAcquire(int ignore) { 
            return compareAndSetState(0, 1); 
        } 
        public boolean tryRelease(int ignore) { 
            setState(0);  
            return true; 
        } 
    } 
 
    private final Sync sync = new Sync(); 
 
    public void lock() {  
        sync.acquire(0);  
    } 
 
    public void unlock() {  
        sync.release(0);  
    } 

java.util.concurrent包中的所有同步工具类都依赖于AQS,其类型程序结构图如下:



AbstractQueuedSynchronizer类还提供了其他一些同步控制方法,包括超时和中断版的获取方法,还集成了独占模式的同步器,如acquireShared,tryReleaseShared等方法。

3.1控制公平
虽然这个队列被设计为FIFO,但并不意味着这个同步器一定是公平的,前面谈到,在tryAcquire检查之后再排队。因此,新线程完全可以偷偷排在第一个线程前面。之所以不采用FIFO,有时候是想获得更高的吞吐量,为了减少等待时间,新到的线程与队列头部的线程一起公平竞争,如果新来的线程比队头的线程快,那么这个新来的线程就获取锁。队头线程失去竞争会再次阻塞,它的继任也将会被阻塞,但这样能避免饥饿。

如果需要绝对公平,那很简单,只需要在tryAcquire方法,不在队头返回false即可。检查是否在队头可以使用getFirstQueuedThread方法。有一情况是,队列是空的,同时有多个线程一拥而入,谁先抢到锁就谁运行,这其实与公平并不冲突,是对公平的补充。

3.2同步器
JAVA并发框架是如何使用AQS的:
ReentrantLock类使用同步状态来代表持有锁的数量,当一个锁被获得,会记录获取该锁的线程身份,如果一个非当前线程试图释放锁是不合法的。该类也使用了ConditionObject类,和一些监视和检查方法。该类支持公平与非公平两种模式,是通过AQS的两个子类来实现的。
ReentrantReadWriteLock类将32位的state分成高位和低位,16位用于写锁计数,其余16位用于读锁计数。
Semaphore类使用同步状态保持当前计数,acquireShared减少计数,tryRelease的增加计数,如果state是正数就唤醒线程。
CountDownLatch类使用同步状态代表计数。所有线程都获得锁时,状态为0,就唤醒。
当然用户可以定义自己的应用程序同步器。例如:事件,集中管理的锁,基于树的障碍等。
分享到:
评论

相关推荐

    Java并发编程学习笔记

    Java并发编程是Java开发中必不可少的一部分,涉及到多线程、同步机制、线程池以及并发工具类等多个核心知识点。以下是对这些主题的详细说明: 1. **线程安全与锁 Synchronized 底层实现原理**: 线程安全是指在多...

    java并发编程:juc、aqs

    理解并熟练掌握JUC和AQS对于Java并发编程至关重要,它们提供了强大且灵活的工具,可以有效解决多线程环境下的同步和通信问题,提高程序的性能和可扩展性。通过自定义AQS的子类,开发者可以根据实际需求构建出满足...

    Java并发编程解析 | 解析AQS基础同步器的设计与实现

    "Java并发编程解析 | 解析AQS基础同步器的设计与实现" 在Java领域中,解决并发编程问题的关键是解决同步和互斥的问题。同步是指线程之间的通信和协作,互斥是指同一时刻只能允许一个线程访问共享资源。Java领域中有...

    多线程与高并发编程笔记、源码等

    标题“多线程与高并发编程笔记、源码等”表明了资源的核心内容,涵盖了多线程和高并发编程的理论与实践。多线程允许一个应用程序同时执行多个任务,而高并发则指系统能够处理大量并发请求的能力。这两个概念在现代...

    图灵Java高级互联网架构师第6期并发编程专题笔记.zip

    01-并发编程之深入理解JMM&并发三大特性(一)-fox 02-并发编程之深入理解JMM&并发三大特性(二)-fox 03-01-HashMap源码解析-monkey 03-并发List、Set、 ConcurrentHashMap底层原理剖析-monkey 04-Java并发线程池...

    JAVA并发编程与高并发解决方案-并发编程四之J.U.C之AQS.docx

    ### JAVA并发编程与高并发解决方案-并发编程四之J.U.C之AQS #### 引言 《JAVA并发编程与高并发解决方案-并发编程四之J.U.C之AQS》是一篇详细介绍Java实用并发工具包(Java Util Concurrency,简称J.U.C.)中重要...

    阿里专家级并发编程架构师课程-网盘链接提取码下载 .txt

    阿里专家级并发编程架构师级课程,完成课程的学习可以帮助同学们解决非常多的JAVA并发编程疑难杂症,极大的提高JAVA并发编程的效率。课程内容包括了JAVA手写线程池,UC线程池API详解,线程安全根因详解,锁与原子类...

    阿里专家级并发编程架构师课程 彻底解决JAVA并发编程疑难杂症 JAVA并发编程高级教程

    阿里专家级并发编程架构师级课程,完成课程的学习可以帮助同学们解决非常多的JAVA并发编程疑难杂症,极大的提高JAVA并发编程的效率。课程内容包括了JAVA手写线程池,UC线程池API详解,线程安全根因详解,锁与原子类...

    Java 并发编程实战.pdf

    在学习并发编程时,理解如何启动和管理线程是基础中的基础。 其次,Java中的同步机制是并发编程中十分重要的部分。它主要通过synchronized关键字来实现,确保在任意时刻只有一个线程可以访问某个方法或者代码块。...

    aqs-并发编程笔记.pdf

    并发编程笔记中的知识点涵盖了保护性暂停模式(Guarded Suspension Design Pattern)的定义、实现与分析,以及在Java中如何通过GuardedObject对象来实现多线程间的结果传递和超时处理。以下是详细的知识点梳理: 1....

    并发编程笔记20190526.docx

    并发编程是计算机科学中的关键领域,特别是在多核处理器和分布式系统中,它允许程序同时执行多个任务,提高系统的效率和响应性。以下是一些关于并发编程的重要知识点: ### 第一章 线程基础、共享与协作 1. **进程...

    Java并发编程:深入解析抽象队列同步器(AQS)及其在Lock中的应用

    本文深入探讨了Java并发编程的关键组件——抽象队列同步器(AQS)及其在ReentrantLock的应用。AQS是处理线程同步问题的高效工具,是Java并发编程中的核心。文章首先简要介绍了并发编程领域的先驱Doug Lea。重点在于...

    【并发编程】简单化理解AQS和ReentrantLock.pdf

    AQS和`ReentrantLock`是Java并发编程中重要的组成部分,通过对它们的理解和掌握,可以更好地设计和实现高性能的并发程序。通过本文的学习,读者可以了解到这些核心概念和技术的实际应用,并能够根据具体的业务需求...

    Java并发编程全景图.pdf

    学习并发编程不仅是对技术的挑战,也是对程序员逻辑思维和问题解决能力的考验。随着硬件性能的持续提升和应用需求的多样化,Java并发编程的实践和理论研究将不断深化,为开发高性能、高可用性的系统提供支持。

    20.9.24aqs-并发编程笔记.pdf

    这种模式主要解决了在并发编程中如何安全高效地进行线程间通信和数据传递的问题。 该模式的基本概念包含以下几个方面: 1. 线程间传递结果:在多线程编程中,某个线程处理的结果需要传递给其他线程。这种传递通常...

    Java并发之AQS详解.pdf

    Java并发之AQS详解 AbstractQueuedSynchronizer(AQS)是 Java 并发编程中的一个核心组件,提供了一套多线程访问共享资源的同步器框架。AQS 定义了两种资源共享方式:Exclusive(独占)和 Share(共享)。在 AQS 中...

    并发编程实战

    《并发编程实战》是一本深度探讨Java多...通过学习《并发编程实战》,开发者不仅能熟练掌握Java多线程编程的基本技巧,还能深入了解并发编程的原理和最佳实践,从而在实际项目中编写出高效、稳定且易于维护的并发代码。

    并发编程,学习手记.pdf

    【并发编程】是计算机科学中的一个关键领域,...学习并发编程不仅需要掌握理论知识,还需要实践经验和对各种工具的熟练运用。通过阅读文档、编写测试案例以及参考各种在线资源,可以不断深化对并发编程的理解和应用。

    aqs-并发编程(2)笔记.pdf

    在并发编程中,保护性暂停模式(Guarded Suspension Design Pattern)是一种常用的同步机制,用于线程间的协作。该模式允许一个线程等待另一个线程的特定操作完成,然后继续执行。在该模式中,线程间共享的某个对象...

    java并发编程-AQS和JUC实战

    ### Java并发编程-AQS和JUC实战 #### 一、ReentrantLock 重入锁 **1.1 概述** - **基本介绍**: `ReentrantLock` 是一个实现了 `Lock` 接口的可重入互斥锁,提供比 `synchronized` 更丰富的功能。与 `synchronized...

Global site tag (gtag.js) - Google Analytics