Java线程同步之一--AQS

线程同步是指两个并发执行的线程在同一时间不同时执行某一部分的程序。同步问题在生活中也很常见,就比如在麦当劳点餐,假设只有一个服务员能够提供点餐服务。每个服务员在同一时刻只能接待一个顾客的点餐,那么除了正在接待的顾客,其他人只能等待排队。当一个点餐服务完成之后,其他顾客就可以上去进行点餐。

从这个例子中可以看到如下几个关注点:

  • 点餐服务为临界区域(critical area),其可同时进行的数量,即为有多少人可进入临界区域。
  • 排队即为对目前暂时无法取得点餐服务的人的一种处理方式。这种处理方式的特性有公平性(按次序),效率性(接手最快为最好)等。
  • 顾客进行排队和从队伍中叫一个顾客来进行服务即为睡眠(park)和唤醒(unpark)机制。

并发中线程同步是重点需关注的问题,线程同步自然也有一定的模式,DougLea就写出了一个简单的框架AQS用来支持一大类线程同步工具,如ReentrantLock,CountdownLatch,Semphaore等。

AQS是concurrent包中的一系列同步工具的基础实现,其提供了状态位,线程阻塞-唤醒方法,CAS操作。基本原理就是根据状态位来控制线程的入队阻塞、出队唤醒来解决同步问题。

入队:

出队:

二、代码分析

下面以ReentrantLock来说明AQS的组成构件的工作情况:

在ReentrantLock中封装了一个同步器Sync,继承了AbstractQueuedSynchronizer,根据对临界区的访问的公平性要求不同,又分为NonfairSync和FairSync。为了简化起见,就取最简单的NonFairSync作为例子来说明:

1. 对于临界区的控制:

java.util.concurrent.locks.ReentrantLock.NonfairSync

   1: final void lock() {
   2:  
   3:         if (compareAndSetState(0, 1))
   4:  
   5:         setExclusiveOwnerThread(Thread.currentThread());
   6:  
   7:         else
   8:  
   9:         acquire(1);
  10:  
  11:     }
  12:  

从以上代码可以看出,其主要目的是采用cas比较临界区的状态。

1.1. 如果为0,将其设置为1,并记录当前线程(当前线程可进入临界区);

1.2. 如果为1,尝试获取临界区控制

java.util.concurrent.locks.AbstractQueuedSynchronizer

   1: public final void acquire(int arg) {
   2:  
   3:             if (!tryAcquire(arg) &&
   4:  
   5:                 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
   6:  
   7:                 selfInterrupt();
   8:  
   9:           }
  10:  

1.2.1. NonFairLock的tryAcquire实现为:

   1: final boolean nonfairTryAcquire(int acquires) {
   2:  
   3:               final Thread current = Thread.currentThread();
   4:  
   5:               int c = getState();
   6:  
   7:               if (c == 0) {
   8:  
   9:               if (compareAndSetState(0, acquires)) {
  10:  
  11:                  setExclusiveOwnerThread(current);
  12:  
  13:                  return true;
  14:  
  15:               }
  16:  
  17:             }
  18:  
  19:              else if (current == getExclusiveOwnerThread()) {
  20:  
  21:                  int nextc = c + acquires;
  22:  
  23:                  if (nextc < 0) // overflow
  24:  
  25:                  throw new Error("Maximum lock count exceeded");
  26:  
  27:                  setState(nextc);
  28:  
  29:                  return true;
  30:  
  31:               }
  32:  
  33:            return false;
  34:  
  35:          }
  36:  

                      上述代码主要是针对大部分线程进入临界区工作时间不会很长而进行的性能优化,第一次尝试失败了,极有可能过一会儿锁就释放了,因此重新去尝试获取锁。

1.2.2. 以下这段代码是锁的精华部分

java.util.concurrent.locks.AbstractQueuedSynchronizer

   1: final boolean acquireQueued(final Node node, int arg) {
   2:  
   3: try {
   4:  
   5: boolean interrupted = false;
   6:  
   7: for (;;) {
   8:  
   9: final Node p = node.predecessor();
  10:  
  11: if (p == head && tryAcquire(arg)) {
  12:  
  13: setHead(node);
  14:  
  15: p.next = null; // help GC
  16:  
  17: return interrupted;
  18:  
  19: }
  20:  
  21: if (shouldParkAfterFailedAcquire(p, node) &&
  22:  
  23: parkAndCheckInterrupt())
  24:  
  25: interrupted = true;
  26:  
  27: }
  28:  
  29: } catch (RuntimeException ex) {
  30:  
  31: cancelAcquire(node);
  32:  
  33: throw ex;
  34:  
  35: }
  36:  
  37: }
  38:  

        在无限循环中完成了对线程的阻塞和唤醒。阻塞在parkAndCheckInterrupt()唤醒后从此处进行释放。

算法过程:

  • 从加入队列的node开始反向查找,将前一个元素赋值给p;
  • 如果p是head,那么试着再获得一次锁tryAcquire(arg),成功则将head指针往后移动,并跳出循环;
  • 如果上一步骤尝试失败,那么进行测试是否要park ,如果状态为0,将其标记为SIGNAL,并返回false;
  • 再重复检查一次,发现其头部的waitStatus为-1.Node.signal。确认需要park successor; 进行parkAndCheckInterrupt()将当前线程阻塞。

2. 对于临界区的释放

2.1. java.util.concurrent.locks.AbstractQueuedSynchronizer

   1: public final boolean release(int arg) {
   2:  
   3: if (tryRelease(arg)) {
   4:  
   5: Node h = head;
   6:  
   7: if (h != null && h.waitStatus != 0)
   8:  
   9: unparkSuccessor(h);
  10:  
  11: return true;
  12:  
  13: }
  14:  
  15: return false;
  16:  
  17: }
  18:  

2.1.1. java.util.concurrent.locks.ReentrantLock.Sync

   1: protected final boolean tryRelease(int releases) {
   2:  
   3: int c = getState() - releases;
   4:  
   5: if (Thread.currentThread() != getExclusiveOwnerThread())
   6:  
   7: throw new IllegalMonitorStateException();
   8:  
   9: boolean free = false;
  10:  
  11: if (c == 0) {
  12:  
  13: free = true;
  14:  
  15: setExclusiveOwnerThread(null);
  16:  
  17: }
  18:  
  19: setState(c);
  20:  
  21: return free;
  22:  
  23: }
  24:  

将state进行变化-releases,检查当前线程是否是拿住锁的线程,否则掷出异常.如果为0,将持有锁线程标记为null。

从ReentrantLock例子可以看出AQS的工作原理,更为精妙的是,在这几个基本机制作用下衍生了许多种并发工具,以后的介绍中可以看到。

Java线程同步之一--AQS的更多相关文章

  1. java 线程同步 原理 sleep和wait区别

    java线程同步的原理java会为每个Object对象分配一个monitor, 当某个对象(实例)的同步方法(synchronized methods)被多个线程调用时,该对象的monitor将负责处 ...

  2. Java线程同步_1

    Java线程同步_1 synchronized 该同步机制的的核心是同步监视器,任何对象都可以作为同步监视器,代码执行结束,或者程序调用了同步监视器的wait方法会导致释放同步监视器 synchron ...

  3. java线程 同步临界区:thinking in java4 21.3.5

    java线程 同步临界区:thinking in java4 21.3.5 thinking in java 4免费下载:http://download.csdn.net/detail/liangru ...

  4. JAVA - 线程同步和线程调度的相关方法

    JAVA - 线程同步和线程调度的相关方法 wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁:wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等 ...

  5. Java线程同步的四种方式详解(建议收藏)

    ​ Java线程同步属于Java多线程与并发编程的核心点,需要重点掌握,下面我就来详解Java线程同步的4种主要的实现方式@mikechen 目录 什么是线程同步 线程同步的几种方式 1.使用sync ...

  6. Java 线程同步组件 CountDownLatch 与 CyclicBarrier 原理分析

    1.简介 在分析完AbstractQueuedSynchronizer(以下简称 AQS)和ReentrantLock的原理后,本文将分析 java.util.concurrent 包下的两个线程同步 ...

  7. Java线程同步类容器和并发容器(四)

    同步类容器都是线程安全的,在某些场景下,需要枷锁保护符合操作,最经典ConcurrentModifiicationException,原因是当容器迭代的过程中,被并发的修改了内容. for (Iter ...

  8. Java线程同步和线程通信

    一.线程同步 当多个线程访问同一个数据时,非常容易出现线程安全问题.这时候就需要用线程同步. 不可变类总是线程安全的,因为它的对象状态是不可改变的,但可变类对象需要额外的方法来保证线程安全. 1.同步 ...

  9. 【总结】Java线程同步机制深刻阐述

    原文:http://hxraid.iteye.com/blog/667437 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread ...

随机推荐

  1. 使用Webbrowser的一点心得体会

    原文:使用Webbrowser的一点心得体会 自从用上VS2005后,发现多了个WebBrowser控件(.net 2003中不带),为图方便吧,有好多小工具就用这个写的,慢慢也有点体会了,总结一下, ...

  2. PDF.NET SOD Ver 5.1完全开源

    PDF.NET SOD Ver 5.1完全开源 前言: 自从我2014年下半年到现在的某电商公司工作后,工作太忙,一直没有写过一篇博客,甚至连14年股票市场的牛市都错过了,现在马上要过年了,而今天又是 ...

  3. MVC模型部分验证

    ASP.NET MVC模型部分验证 在很多情况下,我们为了代码的复用可能会存在ViewModel共用的情形.比方说,web应用中常常会遇到的一个需求就是用户找回密码的功能.用户首先要验证通过验证邮箱( ...

  4. C语言库函数大全及应用实例二

    原文:C语言库函数大全及应用实例二                                              [编程资料]C语言库函数大全及应用实例二 函数名: bioskey 功 能 ...

  5. BZOJ 1208 HNOI2004 宠物收容所 平衡树/set

    标题效果:有一个宠物收容所.目前还没有被采纳的宠物或谁想要领养宠物,每个宠物有个性值,大家谁想要领养宠物具有理想人格值.每一刻,宠物收容所只是为了有谁想要领养宠物或宠物的人. 当领走宠物,将有一定程度 ...

  6. SQL点滴17—使用数据库引擎存储过程,系统视图查询,DBA,BI开发人员必备基础知识

    原文:SQL点滴17-使用数据库引擎存储过程,系统视图查询,DBA,BI开发人员必备基础知识 在开发过程中会遇到需要弄清楚这个数据库什么时候建的,这个数据库中有多少表,这个存储过程长的什么样子等等信息 ...

  7. Java 多并发之原子访问(Atomic Access)

    在编程中,一个原子操作是只会出现一次的.一个原子操作在中间不会停止:要么全部发生要么一点也不发生.我们只有在原子操作完成之后才会看到原子操作的具体影响. 甚至是非常简单的表达式能够构造分解为简单操作的 ...

  8. 安装Visual Source Safe 2005 - 初学者系列 - 学习者系列文章

    本文介绍微软的文档管理工具Visual Source Safe 2005的安装 从下列地址获取该工具: ed2k://|file|en_vss_2005.iso|108048384|C4BEC1EC3 ...

  9. 【SSRS】入门篇(七) -- 报表发布

    原文:[SSRS]入门篇(七) -- 报表发布 完成[SSRS]入门篇(六) -- 分组和总计后,第一份简单的报表就已完成了,下面把报表发布到报表服务器上. (实际情况下,报表展示给用户未必是用报表服 ...

  10. Hypeiron Planning/Essbase修改规划类型名称

    1.修改planning关系库 1.1--修改Plan_type,例如将type_name “Plan1”修改为”Plan1ts”,提交 SELECT * FROM hsp_plan_type FOR ...