Java线程同步之一--AQS

时间:2021-12-02 23:47:11

Java线程同步之一--AQS

Java线程同步之一--AQS

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

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

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

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

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

Java线程同步之一--AQS

入队:

Java线程同步之一--AQS

出队:

Java线程同步之一--AQS

二、代码分析

下面以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线程同步&lowbar;1

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

  3. java线程 同步临界区:thinking in java4 21&period;3&period;5

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

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

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

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

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

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

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

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

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

  8. Java线程同步的方式

     java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),      将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的 ...

  9. Java线程同步(synchronized)——卖票问题

    卖票问题通常被用来举例说明线程同步问题,在Java中,采用关键字synchronized关键字来解决线程同步的问题. Java任意类型的对象都有一个标志位,该标志位具有0,1两种状态,其开始状态为1, ...

随机推荐

  1. Zabbix实现微信报警

    一.  申请企业微信账号,申请地址 https://qy.weixin.qq.com/ 二. 登陆企业微信账 图一 图二 2.添加微信账号 图一 图二 完成以上步骤后 就完成了微信账号的添加 三.新建 ...

  2. 机器学习---python环境搭建

    一 安装python2.7 去https://www.python.org/downloads/ 下载,然后点击安装,记得记住你的安装路径,然后去设置环境变量,这些自行百度一下就好了. 由于2.7没有 ...

  3. POJ1419 &amp&semi; 最大团

    题意: 求一个图的最大点独立集.SOL: 转化为补图的最大团,最大团似乎是一个NP问题,那么只好爆搜了. 补一补图论基础,代码不想打了,来自某blog #include <iostream&gt ...

  4. 获取指定的系统路径 SHGetSpecialFolderPath

    1.获取桌面的系统路径 TCHAR szLink[MAX_PATH + ] = { }; SHGetSpecialFolderPath(,szLink,CSIDL_DESKTOPDIRECTORY,) ...

  5. js中常用framesetiframe页面跳转传参方法实例大全

    logf的空间

  6. Sass函数--列表函数

    列表函数简介 列表函数主要包括一些对列表参数的函数使用,主要包括以下几种: length($list):返回一个列表的长度值: nth($list, $n):返回一个列表中指定的某个标签值  join ...

  7. python自学笔记(二)python基本数据类型之字符串处理

    一.数据类型的组成分3部分:身份.类型.值 身份:id方法来看它的唯一标识符,内存地址靠这个查看 类型:type方法查看 值:数据项 二.常用基本数据类型 int 整型 boolean 布尔型 str ...

  8. unit正交相机Size的计算公式

    如:相机的大小为800*480,要使相机适应800*480像素的图,则 Size = 相机高/2/像素单位 = 480/2/100 = 2.4

  9. HDU2089 暴力打表

    62的判断就是倒过来没有26 hdu2089 #include<cstdio> #include<cstdlib> #include<iostream> #incl ...

  10. 「JavaScript」同步、异步、回调执行顺序之经典闭包setTimeout分析

    聊聊同步.异步和回调 同步,异步,回调,我们傻傻分不清楚, 有一天,你找到公司刚来的程序员小T,跟他说:“我们要加个需求,你放下手里的事情优先支持,我会一直等你做完再离开”.小T微笑着答应了,眼角却滑 ...