多线程进阶,性能优化之无锁

  • 比较交换(CAS)
    • 线程安全整数类:AtomicInteger
    • CAS底层原理:Unsafe类
    • CAS缺点:
    • ABA问题的解决
      • 原子引用:AtomicReference
      • 原子引用时间戳:AtomicStampedReference
博客地址(点击即可访问) github源码地址
深刻理解JMM(JAVA内存模型) https://github.com/zz1044063894/JMMprojcet
volatile详解 https://github.com/zz1044063894/volatile
线程基础,java并发程序基础 https://github.com/zz1044063894/thread-base
线程进阶,JDK并发包 https://github.com/zz1044063894/JDK-concurrency
多线程进阶,性能优化之锁优化 https://github.com/zz1044063894/lock-optimization
线程进阶,性能优化之无锁 https://github.com/zz1044063894/no-lock

对于并发程序来说,锁是一种悲观的策略。它总是假设每一次的临界区操作会产生冲突,因此,对每次操作都小心翼翼。如果多个线程同时需要访问临界区资源,就宁可吸声性能让线程进行等待,所以说锁会阻塞线程执行。而无锁是一种乐观策略,它会假设对资源的访问是没有冲突的,没有冲突就不需要等待。所以所有的线程都可以在不停顿的状态下继续执行,那遇到冲突怎么办?无锁策略使用的是比较交换(CAS Compare And Swap)技术,来判断是否发生冲突,如果发生了冲突,就进行重试,直到不冲突为止

比较交换(CAS)

与锁相比,使用CAS会使程序看起来更加复杂,但是性能会好上很多倍,因为他不会造成死锁问题。线程见的相互影响也远远小于锁的方式,更重要的是,无锁方式没有锁竞争带来的开销。

CAS的算法过程:
包含3个参数CAS(V,E,N)。V表示需要更新的变量,E表示预期值,N表示新值。当V==E时,才会将V的值设置为N,如果V的值和E的值不同,则说明有其他线程已经做了更新,则当前线程什么都不做。最后CAS返回当前V的最新真实值。
CAS是抱着乐观的态度进行工作的,它总是认为自己可以成功完成操作,当多个线程同时使用CAS操作同一个变量时,只有一个会生出,并且更新成功,其他的会失败。失败的线程不会被挂起,仅会被通知更新失败,并且允许重试,当然也允许放弃操作。正式因为这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前的干扰,并作出处理。
请看下面代码:

package com.jingchu.nolock;import java.util.concurrent.atomic.AtomicInteger;/*** @description: 对比交换测试源码* @author: JingChu* @createtime :2020-07-24 08:44:30**/
public class MyCAS {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(5);System.out.println(atomicInteger.compareAndSet(5,2020)+" 现在atomicInteger的值:"+atomicInteger);System.out.println(atomicInteger.compareAndSet(5,724)+" 现在atomicInteger的值:"+atomicInteger);}
}

结果:

true或者false表示交换的结果。

线程安全整数类:AtomicInteger

JDK并发包中有一个aotmic包,里面含有一些可以直接使用CAS操作的线程安全类型。使用如同上述代码。
在本小节我们拿AtomicInteger来讲述,对于其他原子类其操作基本相同。下面我们看提供的一些主要方法:

int get();//获取当前值
void set(int newValue);//设置当前值
int getAndSet(int newValue);//设置新值,并且返回旧值
boolean compareAndSet(int oldValue,int newValue);//如果当前值为oldValue,则设置为newValue
int getAndIncrement();//当前值加1,返回旧值
int getAndDecrement();//当前值减1,返回旧值
int getAndAdd(int addVlaue);//当前值加addVlaue,返回旧值
int incrementAndGet();//当前值加1,返回新值
int decrementAndGet();//当前值减1,返回新值
int addAndGet(int addVlaue);//当前值加addVlaue,返回新值

CAS底层原理:Unsafe类

之前的博客我们讲过多线程执行i++的代码,接下来我们看底层源码。


分析:第一个方法this指的是当前对象,valueoffset值偏移量,i要加的值,
接下来跳到第二个方法,var1当前对象,var2内存地址,var4要加的值,固定为1,最终返回结果

在这里可以看到一个变量unsafe,它是Unsafe类型,从名字上看,这个类封装了一些不安全的操作。总所周知,java中是没有指针的,因为指针式不安全的,如果指针指错了为止,或者计算指针偏移量时出错,结果可能是灾难性的,很有可能覆盖掉别人的内存,导致系统崩盘。
而Unsafe类提供了一些类似指针的操作。

CAS缺点:

  • 循环时间长,开销大
    从上面我们直到,CAS的特点原子实现i++操作时,有个dowhile循环,如果该线程一直失败,一直重试。当高并发下,这个错误可能有多个线程一起产生,那么就会产生很大的性能问题
  • 只能保证一个共享变量的原子性
  • ABA 问题
    狸猫换太子:线程1准备用CAS将变量的值由A替换为C,在此之前,线程2将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题。

ABA问题的解决

原子引用:AtomicReference

AtomicReference和AtomicInteger类似,不同指出在于AtomicInteger是对整数的封装,而AtomicReference是对普通对象的引用。也就是说它可以保证你在修改对象引用时的线程安全性。
请看下面代码:

package com.jingchu.nolock;import java.util.concurrent.atomic.AtomicReference;/*** @description: 原子对象引用 测试代码* @author: JingChu* @createtime :2020-07-24 14:25:12**/
public class MyAtomicReference {public static void main(String[] args) {User user1 = new User("zhangsan",23);User user2 = new User("lisi",24);AtomicReference<User> atomicReference = new AtomicReference<>();atomicReference.set(user1);System.out.println(atomicReference.compareAndSet(user1,user2)+"\t "+atomicReference.get().toString());System.out.println(atomicReference.compareAndSet(user1,user2)+"\t "+atomicReference.get().toString());}}
class User{String name;int age;@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}public User(String name, int age) {this.name = name;this.age = age;}
}

运行结果:

原子引用时间戳:AtomicStampedReference

AtomicReference无法解决上述ABA问题,因为对象在修改的过程中,丢失了状态信息。对象本身与状态都被画上了等号。因此,我们只需要能够记录对象修改过程中的状态值,就可以解决ABA问题了。

AtomicStampedReference不仅维护了对象值,还维护了一个版本号(许多人称为时间戳,个人感觉版本号更加标准)。当版本号一直的时候,才允许更新,每次更新的时候都会更新一下版本号。

package com.jingchu.nolock;import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;/*** @description: 带有时间戳对象引用实例* @author: JingChu* @createtime :2020-07-24 14:35:19**/
public class MyAtomicStampedReference {static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);public static void main(String[] args) {System.out.println("有ABA问题的形式:------------------------------");//未使用版本号管理new Thread(() -> {atomicReference.compareAndSet(100, 101);System.out.println(Thread.currentThread().getName() + "\t " + atomicReference.get());atomicReference.compareAndSet(101, 100);System.out.println(Thread.currentThread().getName() + "\t " + atomicReference.get());}, "t1").start();new Thread(() -> {System.out.println(Thread.currentThread().getName() + "\t sleep 1s \t" + atomicReference.get());try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}atomicReference.compareAndSet(100, 2020);System.out.println(Thread.currentThread().getName() + "\t " + atomicReference.get());}, "t2").start();System.out.println("解决了ABA问题的形式:------------------------------");//使用版本号管理new Thread(() -> {int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t 第1次拿到的版本号:" + stamp);try {//线程休息1s,为了保证t4线程也获得这个版本号Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}atomicStampedReference.compareAndSet(100, 101,stamp, stamp + 1);System.out.println(Thread.currentThread().getName() + "\t 第2次拿到的版本号:" + atomicStampedReference.getStamp());atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);System.out.println(Thread.currentThread().getName() + "\t 第3次拿到的版本号:" + atomicStampedReference.getStamp());}, "t3").start();new Thread(() -> {int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t 第1次拿到的版本号:" + stamp);try {//线程休息2s,为了保证t3线程已经修改版本号Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}boolean falg = atomicStampedReference.compareAndSet(100, 2020,stamp, stamp + 1);System.out.println(Thread.currentThread().getName() + "\t 修改结果:" + falg + "" +"\t 第2次版本号" + atomicStampedReference.getStamp()+"\t 现在的值"+atomicStampedReference.getReference());falg = atomicStampedReference.compareAndSet(100, 2020,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);System.out.println(Thread.currentThread().getName() + "\t 修改结果:" + falg + "" +"\t 第3次版本号" + atomicStampedReference.getStamp()+"\t 现在的值"+atomicStampedReference.getReference());}, "t4").start();}
}


这些代码笔者就不用解释太多了,都在代码的注释里了。

【JUC多线程与高并发】线程进阶,性能优化之无锁相关推荐

  1. 尚硅谷-互联网大厂高频重点面试题 (第2季)JUC多线程及高并发

    本期内容包括 JUC多线程并发.JVM和GC等目前大厂笔试中会考.面试中会问.工作中会用的高频难点知识. 斩offer.拿高薪.跳槽神器,对标阿里P6的<尚硅谷_互联网大厂高频重点面试题(第2季 ...

  2. JUC多线程及高并发

    请你谈谈对Volatile的理解 volatile是Java虚拟机提供的轻量级的同步机制 保证可见性 不保证原子性 禁止指令重排(保证有序性) JMM内存模型之可见性 JMM(Java内存模型Java ...

  3. 多线程与高并发(二):解析自旋锁CAS操作与volatile

    Volitile 作用:保证线程的可见性,同时禁止指令的重排序 多线程时,存在的问题在于,在一个线程中对副本的更改并没有及时地反映到另外一个线程中.这就是线程之间的不可见. 对变量值加了 voliti ...

  4. java基础巩固-宇宙第一AiYWM:为了维持生计,多高(多线程与高并发)_Part1~整起(线程与进程篇:线程概念、线程状态、线程死锁)

    这个题目我感觉很多大哥大姐和我一样,虽然夹在众位大哥大姐中跟着一块喊着"多线程与高并发"的口号,但是这里面其实包含的东西并不像名字里面这么少.现在就开始咱们的旅程吧. 特此感谢,低 ...

  5. java多线程实例_多线程&高并发(全网最新:面试题+导图+笔记)面试手稳心不慌...

    前言 当你开始开始去跳槽面试的时候,明明只是一份15K的工作,却问你会不会多线程,懂不懂高并发,火箭造得让你猝及不防,结果就是凉凉:现如今市场,多线程.高并发编程.分布式.负载均衡.集群等可以说是现在 ...

  6. 多线程&高并发(全网最新:面试题 + 导图 + 核心学习笔记)面试手稳心不慌,轻松拿下 offer,秋招跳槽必不可少的底层能力

    前言 当你开始开始去跳槽面试的时候,明明只是一份 15K 的工作,却问你会不会多线程,懂不懂高并发,火箭造得让你猝及不防,结果就是凉凉:现如今市场,多线程.高并发编程.分布式.负载均衡.集群等可以说是 ...

  7. 【闲聊杂谈】直击重灾区 - 多线程与高并发

    在开始学习多线程与高并发的知识之前,我想先问一个问题:你平时在写代码的时候,有没有刻意的去思考如何压榨CPU性能?其实纵观整个编程的发展历史,其实就是一部对于CPU性能压榨的血泪史. 单进程人工切换 ...

  8. 多线程与高并发整理总结【超全面】

    我们先说一下为什么要讲多线程和高并发? 原因是,你想拿到一个更高的薪水,在面试的时候呈现出了两个方向的现象: 第一个上天 项目经验 高并发.缓存.大流量.大数据量的架构设计 第二个入地 各种基础算法, ...

  9. 多线程导出excel高并发_大牛带你深入java多线程与高并发:JMH与Disruptor,确定能学会?...

    前言 今天我们讲两个内容,第一个是JMH,第二个是Disruptor.这两个内容是给大家做更进一步的这种多线程和高并发的一些专业上的处理.生产环境之中我们很可能不自己定义消息队列,而是使用 Disru ...

最新文章

  1. 原 史上最简单的SpringCloud教程 | 第八篇: 消息总线(Spring Cloud Bus)(Finchley版本)
  2. 树莓派 Zero 与 Zero W 对比
  3. 如何在vscode运行php代码_如何提高 PHP 代码的质量?
  4. proguard java 教程,[Gradle中文教程系列]-跟我学Gradle-使用proguard混淆你的spring boot应用...
  5. 我发现了pandas的黄金搭档!
  6. 实现GridView的插入功能
  7. C++ 基类和派生类的构造函数
  8. Selenium2+python自动化75-非input文件上传(SendKeys)
  9. 移动端上下拖动调整顺序效果_移动端上下滑动事件之--坑爹的touch.js
  10. 算法学习总结(1)——基本数据结构
  11. jetson刷机遇到cuda装不了
  12. 全面解析腾讯最新开源 loT 操作系统 TencentOS tiny!
  13. idea2016 android genyomtion,Android Studio Genyomtion配置
  14. argparse模块
  15. 周星驰八级全国统一试卷
  16. 药事管理学名词解释和问答题题集
  17. 绘画教程:动漫人体肌肉的详细画法
  18. 聚类系数与小世界网络
  19. JMeter 安装教程
  20. uhs3内存卡有哪些_什么是UHS超高速SD卡?三代有什么区别

热门文章

  1. 微信公众号html怎么做,微信公众号怎么做页面模板?
  2. JsonAjaxi18n
  3. c# 旋转矩形 的简单实现
  4. Explore the Sky丨来 TiDB Hackathon 2021 探索无限可能
  5. IdentityServer4服务器配置
  6. 人工智能风口已过?不,其实才刚刚开始...
  7. 【转】ubuntu学习心得之SATA硬盘和IDE硬盘
  8. 软件测试方案制作,如何制作移动app测试方案及详细流程?
  9. springboot工程中maven插件浅析
  10. Java语言中的逻辑运算符