CAS和AQS

CAS

全称(Compare And Swap),比较交换

Unsafe类是CAS的核心类,提供硬件级别的原子操作

 

// 对象、对象的地址、预期值、修改值
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

缺点:

  1. 开销大:在并发量比较高的情况下,如果反复尝试更新某个变量,却又一直更新不成功,会给CPU带来较大的压力
  2. ABA问题:当变量从A修改为B在修改回A时,变量值等于期望值A,但是无法判断是否修改,CAS操作在ABA修改后依然成功。
    • 如何避免:Java提供了AtomicStampedReference和AtomicMarkableReference来解决。AtomicStampedReference通过包装[E,Integer]的元组来对对象标记版本戳stamp,对于ABA问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加1,即A —> B —> A,变成1A —> 2B —> 3A。
  3. 不能保证代码块的原子性:CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。

 

public class Test {private static AtomicInteger atomicInteger = new AtomicInteger(100);private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);public static void main(String[] args) throws InterruptedException {//AtomicIntegerThread at1 = new Thread(new Runnable() {@Overridepublic void run() {atomicInteger.compareAndSet(100,110);atomicInteger.compareAndSet(110,100);}});Thread at2 = new Thread(new Runnable() {@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(2);      // at1,执行完} catch (InterruptedException e) {e.printStackTrace();}System.out.println("AtomicInteger:" + atomicInteger.compareAndSet(100,120));}});at1.start();at2.start();at1.join();at2.join();//AtomicStampedReferenceThread tsf1 = new Thread(new Runnable() {@Overridepublic void run() {try {//让 tsf2先获取stamp,导致预期时间戳不一致TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}// 预期引用:100,更新后的引用:110,预期标识getStamp() 更新后的标识getStamp() + 1atomicStampedReference.compareAndSet(100,110,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);atomicStampedReference.compareAndSet(110,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);}});Thread tsf2 = new Thread(new Runnable() {@Overridepublic void run() {int stamp = atomicStampedReference.getStamp();try {TimeUnit.SECONDS.sleep(2);      //线程tsf1执行完} catch (InterruptedException e) {e.printStackTrace();}System.out.println("AtomicStampedReference:" +atomicStampedReference.compareAndSet(100,120,stamp,stamp + 1));}});tsf1.start();tsf2.start();}}

AQS(AbstractQueuedSynchronizer)

维护一个volatile int state(代表共享资源状态)和一个FIFO线程等待队列。

模板方法基本分为三类:

  • 独占锁
  • 共享锁
  • 释放锁

资源共享的方式

  1. Exclusive(独占,只有一个线程能执行,如ReentrantLock)
  2. Share(共享,多个线程可以同时执行,如Semaphore/CountDownLatch)

同步队列

AQS依靠同步队列(一个FIFO的双向队列)来完成同步状态的管理。当当前线程获取状态失败后,同步器会将当前线程以及等待信息构造成一个节点(Node),并尝试将他加入到同步队列。Head节点不保存等待的线程信息,仅通过next指向队列中第一个保存等待线程信息的Node。

 

双向同步队列

Node类

源码(中字注释)

 

static final class Node {/** 代表共享模式 */static final Node SHARED = new Node();/** 代表独占模式 */static final Node EXCLUSIVE = null;/** 以下四个状态解释见下文等待状态 */static final int CANCELLED =  1;static final int SIGNAL    = -1;static final int CONDITION = -2;static final int PROPAGATE = -3;/** 标识等待状态,通过CAS操作更新,原子操作不会被打断*/volatile int waitStatus;/** 当前节点的前置节点 */volatile Node prev;/** 当前节点的后置节点 */volatile Node next;/** 该节点关联的线程(未能获取锁,进入等待的线程) */volatile Thread thread;/** 指向下一个在某个条件上等待的节点,或者指向 SHARE 节点,表明当前处于共享模式*/Node nextWaiter;/*** 判断是否处于共享模式*/final boolean isShared() {return nextWaiter == SHARED;}/** * 返回当前节点的前置节点 * 会做对前置节点空值判断 */final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}Node() {    // Used to establish initial head or SHARED marker}Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}
}

等待状态:

等待状态的修改是CAS原子操作

  • CANCELED: 1,因为等待超时 (timeout)或者中断(interrupt),节点会被置为取消状态。处于取消状态的节点不会再去竞争锁,也就是说不会再被阻塞。节点会一直保持取消状态,而不会转换为其他状态。处于 CANCELED 的节点会被移出队列,被 GC 回收。
  • SIGNAL: -1,表明当前的后继结点正在或者将要被阻塞(通过使用 LockSupport.pack 方法),因此当前的节点被释放(release)或者被取消时(cancel)时,要唤醒它的后继结点(通过 LockSupport.unpark 方法)。
  • CONDITION: -2,表明当前节点在条件队列中,因为等待某个条件而被阻塞。
  • PROPAGATE: -3,在共享模式下,可以认为资源有多个,因此当前线程被唤醒之后,可能还有剩余的资源可以唤醒其他线程。该状态用来表明后续节点会传播唤醒的操作。需要注意的是只有头节点才可以设置为该状态(This is set (for head node only) in doReleaseShared to ensure propagation continues, even if other operations have since intervened.)。
  • 0:新创建的节点会处于这种状态

锁的获取与释放:

获取独占锁

 

获取独占锁

  • acquire方法

 

public final void acquire(int arg) {// 首先尝试获取锁,如果获取失败,会先调用 addWaiter 方法创建节点并追加到队列尾部// 然后调用 acquireQueued 阻塞或者循环尝试获取锁if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){// 在 acquireQueued 中,如果线程是因为中断而退出的阻塞状态会返回 true// 这里的 selfInterrupt 主要是为了恢复线程的中断状态selfInterrupt();}
}

释放独占锁

 

释放独占锁

​ 在独占模式中,锁的释放由于没有其他线程竞争,相对简单。锁释放失败的原因是由于该线程本身不拥有锁,而非多线程竞争。锁释放成功后会检查后置节点的状态,找到合适的节点,调用unparkSuccessor方法唤醒该节点所关联的线程。

  • release方法

 

public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;// waitStatus 为 0,证明是初始化的空队列或者后继结点已经被唤醒了if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
}

获取共享锁

 

获取共享锁

  • acquireShared方法

    通过该方法可以申请锁

 

public final void acquireShared(int arg) {// 如果返回结果小于0,证明没有获取到共享资源if (tryAcquireShared(arg) < 0)doAcquireShared(arg);
}
  • doAcquireShared

 

private void doAcquireShared(int arg) {final Node node = addWaiter(Node.SHARED);boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCif (interrupted)selfInterrupt();failed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

释放共享锁



作者:RealityVibe
链接:https://www.jianshu.com/p/2a48778871a9
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/386531.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统盘点Android开发者必须掌握的知识点,全网疯传

最近在知乎上&#xff0c;有许多人在邀请我去回答“Android前景怎么样、是不是要凉了、是不是应该考虑要转行&#xff1f;”等一系列的问题。 想着可能有很多人都有这样的担心&#xff0c;于是就赶紧写篇文章&#xff0c;来跟你们谈下Android开发的前景到底怎么样&#xff1f;…

数据库操作DDL

show database; 查看所有数据库 drop database db_name; 删除数据库 create database db_name;创建数据库 一个数据库对应一个文件夹 create database if not exists db_name; show warnings; 查看所有警告 show create databae db_name;查看创建的数据库 create database if n…

细数Android开发者的艰辛历程,已拿offer附真题解析

笼统来说&#xff0c;中年程序员容易被淘汰的原因其实不外乎三点。 1、输出能力已到顶点。这个人奋斗十来年了&#xff0c;依旧碌碌无为&#xff0c;很明显这人的天花板就这样了&#xff0c;说白了&#xff0c;天赋就这样。 2、适应能力越来越差。年纪大&#xff0c;有家庭&…

原子操作类AtomicInteger详解

为什么需要AtomicInteger原子操作类&#xff1f; 对于Java中的运算操作&#xff0c;例如自增或自减&#xff0c;若没有进行额外的同步操作&#xff0c;在多线程环境下就是线程不安全的。num解析为numnum1&#xff0c;明显&#xff0c;这个操作不具备原子性&#xff0c;多线程并…

移动端Rem之讲解总结

日妈常说的H5页面&#xff0c;为啥叫H5页面嘛&#xff0c;不就是手机上展示的页面吗&#xff1f;那是因为啊手机兼容所有html5新特性&#xff0c;所以跑在手机上的页面也叫h5页面&#xff0c;跨平台&#xff08;安装ios),基于webview&#xff0c;它就是终端开发的一个组件&…

终于有人把安卓程序员必学知识点全整理出来了,送大厂面经一份!

除了Bug&#xff0c;最让你头疼的问题是什么&#xff1f;单身&#xff1f;秃头&#xff1f;996?面试造火箭&#xff0c;工作拧螺丝&#xff1f; 作为安卓开发者&#xff0c;除了Bug&#xff0c;经常会碰到下面这些问题&#xff1a; 应用卡顿&#xff0c;丢帧&#xff0c;屏幕画…

mq引入以后的缺点

系统可用性降低? 一旦mq不能使用以后,系统A不能发送消息到mq,系统BCD无法从mq中获取到消息.整个系统就崩溃了. 如何解决: 系统复杂程度增加? 加入mq以后,mq引入来的问题很多,然后导致系统的复杂程度增加. 如何解决 系统的一致性降低? 有人给系统A发送了一个请求,本来这个请求…

网易云的朋友给我这份339页的Android面经,成功入职阿里

IT行业的前景 近几年来&#xff0c;大数据、人工智能AI、物联网等一些技术不断发展&#xff0c;也让人们看到了IT行业的繁荣与良好的前景。越来越多的高校学府加大了对计算机的投入&#xff0c;设立相应的热门专业来吸引招生。当然也有越来越多的人选择从事这个行业&#xff0…

git介绍和常用操作

转载于:https://www.cnblogs.com/kesz/p/11124423.html

网易云的朋友给我这份339页的Android面经,满满干货指导

想要成为一名优秀的Android开发&#xff0c;你需要一份完备的知识体系&#xff0c;在这里&#xff0c;让我们一起成长为自己所想的那样~。 25%的面试官会在头5分钟内决定面试的结果60%的面试官会在头15分钟内决定面试的结果 一般来说&#xff0c;一场单面的时间在30分钟左右&…

synchronized 和Lock区别

synchronized实现原理 Java中每一个对象都可以作为锁&#xff0c;这是synchronized实现同步的基础&#xff1a; 普通同步方法&#xff0c;锁是当前实例对象静态同步方法&#xff0c;锁是当前类的class对象同步方法块&#xff0c;锁是括号里面的对象 当一个线程访问同步代码块…

美团安卓面试,难道Android真的凉了?快来收藏!

我所接触的Android开发者&#xff0c;百分之九十五以上 都遇到了以下几点致命弱点&#xff01; 如果这些问题也是阻止你升职加薪&#xff0c;跳槽大厂的阻碍。 那么我确信可以帮你突破瓶颈&#xff01; 1.开发者的门越来越高&#xff1a; 小厂的机会少了&#xff0c;大厂…

django -- 实现ORM登录

前戏 上篇文章写了一个简单的登录页面&#xff0c;那我们可不可以实现一个简单的登录功能呢&#xff1f;如果登录成功&#xff0c;给返回一个页面&#xff0c;失败给出错误的提示呢&#xff1f; 在之前学HTML的时候&#xff0c;我们知道&#xff0c;网页在往服务器提交数据的时…

美团点评APP在移动网络性能优化的实践,通用流行框架大全

" 对于程序员来说&#xff0c;如果哪一天开始他停止了学习&#xff0c;那么他的职业生涯便开始宣告消亡。” 高薪的IT行业是众多年轻人的职业梦想&#xff0c;然而&#xff0c;一旦身入其中却发觉没有想像中那么美好。被称为IT蓝领的编程员&#xff0c;工作强度大&#xf…

centos7.0利用yum快速安装mysql8.0

我这里直接使用MySQL Yum存储库的方式快速安装&#xff1a; 抽象 MySQL Yum存储库提供用于在Linux平台上安装MySQL服务器&#xff0c;客户端和其他组件的RPM包。这些软件包还可以升级和替换从Linux发行版本机软件存储库安装的任何第三方MySQL软件包&#xff0c;如果可以从MySQL…

腾讯3轮面试都问了Android事件分发,论程序员成长的正确姿势

前言 这些题目是网友去美团等一线互联网公司面试被问到的题目。笔者从自身面试经历、各大网络社交技术平台搜集整理而成&#xff0c;熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。 主要分为以下几部分&#xff1a; &#xff08;1&#xff09;Android面试题 …

happens-before规则和as-if-serial语义

概述 本文大部分整理自《Java并发编程的艺术》&#xff0c;温故而知新&#xff0c;加深对基础的理解程度。 指令序列的重排序 我们在编写代码的时候&#xff0c;通常自上而下编写&#xff0c;那么希望执行的顺序&#xff0c;理论上也是逐步串行执行&#xff0c;但是为了提高…

贴片晶振无源石英谐振器直插晶振

贴片晶振 贴片晶振3.579M~25MHz无源石英谐振器直插晶振 文章目录 贴片晶振前言一、贴片晶振3.579M~25MHz无源石英谐振器直插晶振二、属性三、技术参数总结前言 贴片晶振(Surface Mount Crystal Oscillator)是一种采用表面贴装技术进行安装的晶振。它的主要特点是封装小巧、安…

这些新技术你们都知道吗?成功收获美团,小米安卓offer

前言 近期被两则消息刷屏&#xff0c;【字节跳动持续大规模招聘&#xff0c;全年校招超过1万人】【腾讯有史以来最大规模的校招启动】当然Android岗位也包含在内&#xff0c;因此Android还是有很多机会的。结合往期面试的同学&#xff08;主要是校招&#xff09;经验&#xff…

这些新技术你们都知道吗?看这一篇就够了!

前言 现在已经进入招聘季节&#xff0c;本篇文章旨在分享知名互联网企业面试官面试方法和心得&#xff0c;希望通过本文的阅读能给程序员带来不一样的面试体验和感受&#xff0c;放松面试心态&#xff0c;积极备战&#xff01; 面试题 PS&#xff1a;由于文章篇幅问题&#x…