Java多线程CAS操作原理代码实例解析

CAS操作号称无锁优化,也叫作自旋;对于一些常见的操作需要加锁,然后jdk就提供了一些以Atomic开头的类,这些类内部自动带了锁,当然这里的锁并非是用synchronized来实现的,而是通过CAS操作来实现的;

一、下面是 AtomicInteger 的使用:

package com.designmodal.design.juc01;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author D-L
 * @Classname T03_AtomicInteger
 * @Version 1.0
 * @Description 使用 AtomicInteger 类解决常见的 多线程count++
 *        其内部使用了CAS操作来保证原子性 但是不能保证多个方法连续调用都是原子性
 * @Date 2020/7/21 0:35
 */
public class T03_AtomicInteger {
  //使用AtomicInteger类
  AtomicInteger count = new AtomicInteger(0);

  public void m(){
    for (int i = 0; i < 10000; i++) {
      //等同于 在 count++ 上加锁
      count.incrementAndGet();
    }
  }

  public static void main(String[] args) {
    T03_AtomicInteger t = new T03_AtomicInteger();
    List<Thread> threads = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
      threads.add(new Thread(t::m , "Thread" + i));
    }
    threads.forEach((o) -> o.start());
    threads.forEach(o ->{
      try {
        o.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    });

    System.out.println(t.count);
  }
}

二、当然达到使用的级别很简单,看一下API就好了,通过上面的小程序,下面主要来聊一聊原理:

1、通过源码分析AtomicInteger

首先小程序中定义了一个 AtomicInteger 类型的变量count;

 AtomicInteger count = new AtomicInteger(0);
 public void add(){
  count.incrementAndGet();
 }

调用了AtomicInteger类中incrementAndGet();

/**
   * Atomically increments by one the current value.
   *
   * @return the updated value
   */
  public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
  }

调用unsafe类中的 getAndAddInt(Object var1, long var2, int var4)方法;

public native int getIntVolatile(Object var1, long var2);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

 public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
      var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
  }

这里通过以上三步的操作,最终会进入Unsafe类这里调用的 compareAndSwapInt 意思就是比较然后交换,通过一个while循环,在这里转呀转,直到修改成功;

CAS(compareAndSwap)(比较并交换):原来想改变的值为0 ,现在想修改成1 ,这里想做到线程安全就必须要加synchronized,现在想用另外一种方式来替换加锁的方法,就是所谓的CAS操作;你可以把它想象成拥有三个参数的方法cas(V , Expected , NewValue); 第一个参数V是你要改的那个值,Expected第二个参数是你期望当前的值是多少(也就是如果没有线程修改的时,这个值应该是多少,如果不是期望值那就证明有别的线程修改过),NewValue是要设置的新值;

上图简单模拟了CAS操作的过程,当线程1和线程2同时读取了共享变量count = 0;在线程1修改的过程中,线程2已经将count值修改为1,那么在线程1修改的时候发现Expected值和V已经匹配不上了,证明已经有线程快我一步将count值改了(可能这里并发量大的时候已经有n多个线程已经修改过了),怎么办呢?那我只能将我的期望值修改成V的值、newValue 在这基础上加1,然后继续在这自旋操作,直到修改成功,这就是自旋操作;

2、Unsafe类(java并发包底层实现的核心)

CAS操作不需要加锁是如何做到的呢?原因就在于Unsafe这个类,这个类除了你使用反射之外,你是不能够直接使用的,这里不能使用的原因和ClassLoader有关系,所有AtomicXXX 类内部都是CompareAndSwap / CompareAndSet(新版jdk),这个类中存在好多native方法;

Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,一旦能够直接操作内存,这也就意味着(1)不受JVM管理,也就意味着无法被GC,需要我们手动GC,稍有不慎就会出现内存泄漏。(2)Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉。(3)直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率。

CAS:CompareAndSwap,内存偏移地址(var2),预期值(var4),新值(var5)。如果变量在当前时刻的值和预期值expected相等,尝试将变量的值更新为新值(var5)。如果更新成功,返回true;否则,返回false。

/**
   * CAS操作 :Unsafe类中的本地方法 由于Java语言无法访问操作系统底层信息(比如:底层硬件设备等),
   * 这时候就需要借助C C++语言来完成了
   * @param var1 对象
   * @param var2 偏移量
   * @param var4 期望值
   * @param var5 新值
   * @return 修改成功返回true 失败返回false
   */
  public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

  public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

  public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

三、CAS操作带来的ABA问题

ABA问题说白了就是在线程进行CAS操作过程中有多个线程对这个共享变量进行修改,有加有减,兜兜转转又回到起始值,这时该线程浑然不知;打个不恰当的比喻:这个过程就好像你前女友跟你分手以后,在时隔一年之后又找你复合来了,说兜兜转转还是觉得你好,在此期间你前女友已经换了几个男朋友你却浑然不知,那个好看的她穿着你喜欢的小短裙,扎着清纯的马尾辫又回来,好了言归正传,意思就是结果是你期望的,可是这个值是经过很多版本的。

下面简单模拟ABA操作图:

如何解决ABA问题呢?

如果是int类型,最终的值也是你期望的,真的是没有所谓,你也不用去纠结这问题;如果你确实就想管一管,那就加一个版本号,做一次修改操作加一,比较检查时连带版本号一起检查。

基础类型:没有必要管,对你真的没有所谓;

引用类型:就像是你女朋友和你分手之后又复合,中间经历了多少个男朋友,这个是有所谓的,这时可以通过加版本号来解决;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • java使用MulticastSocket实现基于广播的多人聊天室

    使用MulticastSocket实现多点广播: (1)DatagramSocket只允许数据报发给指定的目标地址,而MulticastSocket可以将数据报以广播的方式发送到多个客户端. (2)IP协议为多点广播提供了这批特殊的IP地址,这些IP地址的范围是:224.0.0.0至239.255.255.255.. (3)MulticastSocket类时实现多点广播的关键,当MulticastSocket把一个DaragramPocket发送到多点广播的IP地址时,该数据报将会自动广播到加入

  • Java编程cas操作全面解析

    CAS 指的是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令.这个指令会对内存中的共享数据做原子的读写操作. 简单介绍一下这个指令的操作过程:首先,CPU 会将内存中将要被更改的数据与期望的值做比较.然后,当这两个值相等时,CPU 才会将内存中的数值替换为新的值.否则便不做操作.最后,CPU 会将旧的数值返回.这一系列的操作是原子的.它们虽然看似复杂,但却是 Java 5 并发机制优于原有锁机制的根本.简单来说,CAS 的含义是"我认为原有的值应该是什么,如果是,则将原有的

  • java实现cassandra高级操作之分页实例(有项目具体需求)

    上篇博客讲到了cassandra的分页,相信大家会有所注意:下一次的查询依赖上一次的查询(上一次查询的最后一条记录的全部主键),不像mysql那样灵活,所以只能实现上一页.下一页这样的功能,不能实现第多少页那样的功能(硬要实现的话性能就太低了). 我们先看看驱动官方给的分页做法 如果一个查询得到的记录数太大,一次性返回回来,那么效率非常低,并且很有可能造成内存溢出,使得整个应用都奔溃.所以了,驱动对结果集进行了分页,并返回适当的某一页的数据. 一.设置抓取大小(Setting the fetch

  • Java并发编程总结——慎用CAS详解

    一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能. 2.对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized.以java.util.concurrent.atomic包中AtomicInteger类为例,其getAn

  • 解决cannot be cast to javax.servlet.Filter 报错的问题

    cannot be cast to javax.servlet.Filter 报错, 原因servlet-api.jar冲突 使用maven开发web应用程序, 启动的时候报错: jar not loaded. See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/Servlet.class 然后输出错误: 严重: Exception starting filter encodingFilter java.lang

  • 全面了解Java中的CAS机制

    前言 在看到Java锁机制的时候,无意中看到了CAS这个词,然后在百度查找CAS看了很多文章始终没有看的太懂,今天又在Google上查找了一些资料,才算是真正弄清楚了CAS机制. 什么是CAS 在jdk 1.5中增加的一个最主要的支持是Atomic类,比如说AtomicInteger, AtomicLong,这些类可帮助最大限度地减少在多线程中对于一些基本操作(例如,增加或减少多个线程之间共享的值)的复杂性.而这些类的实现都依赖于CAS(compare and swap)的算法. 乐观锁和悲观锁

  • Java CAS底层实现原理实例详解

    这篇文章主要介绍了Java CAS底层实现原理实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.CAS(compareAndSwap)的概念 CAS,全称Compare And Swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制. CAS(V, A, B),V为内存地址.A为预期原值,B为新值.如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值.否则,说明已经被其他线程更新,处理器不做任何操作:无论哪种情

  • java中switch case语句需要加入break的原因解析

    java中switch case语句需要加入break的原因解析            java 中使用switch case语句需要加入break 做了具体的实例分析,及编译源码,在源码中分析应该如何使用,大家可以参考下: 假设我们有如下这样一个switch语句: public static void test(int index) { switch (index) { case 1: System.out.println(1); case 2: System.out.println(2);

  • Java多线程CAS操作原理代码实例解析

    CAS操作号称无锁优化,也叫作自旋:对于一些常见的操作需要加锁,然后jdk就提供了一些以Atomic开头的类,这些类内部自动带了锁,当然这里的锁并非是用synchronized来实现的,而是通过CAS操作来实现的: 一.下面是 AtomicInteger 的使用: package com.designmodal.design.juc01; import java.util.ArrayList; import java.util.List; import java.util.concurrent.

  • Java多线程状态及方法实例解析

    这篇文章主要介绍了Java多线程状态及方法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 首先介绍线程的五种状态: 新生态:New Thread() 就绪态:准备抢CPU时间片 运行态:抢到了CPU时间片 阻塞态:放弃已经抢到的CPU时间片,且暂时不参与争抢 死亡态:Run运行完了之后 接下来介绍三种方法:线程的阻塞,线程的优先级设置,线程的礼让 public class MutliThreadDemo4 { public static

  • JAVA对象clone方法代码实例解析

    1.Cloneable接口 这个接口一个方法都没有,但是要使用clone方法,就必须实现这个接口,否则抛出CloneNotSupportedException异常 2.clone方法的返回值 先附上两个类代码 package pojo; /** * @Author : ZGQ * @Date : 2020/3/11 22:12 * @Version : 1.0 */ public class Person implements Cloneable{ String name; int age; p

  • Java Session验证码案例代码实例解析

    案例 用户输入用户名,密码以及验证码. 如果用户名和密码输入有误,跳转登录页面,提示:用户名或密码错误 如果验证码输入有误,跳转登录页面,提示:验证码错误 如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您思路 在生成验证码的时候将它的值存入到session中,在比对的时候再取出来进行对比 代码index.jsp <%-- Created by IntelliJ IDEA. User: tanglei Date: 2020/6/26 Time: 下午12:48 To ch

  • Java ArrayList遍历修改代码实例解析

    用for-each 边遍历ArrayList 边修改时: public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("aa"); list.add("bb"); list.add("cc"); list.add("dd"); System.out.println(list);

  • Java 代码实例解析设计模式之监听者模式

    代码展示 Main:测试类 ObServer:每个被监听的对象实现该接口,重写该方法,完成自己的业务 public interface ObServer { /** * 当某一个被监控的对象发生变化时 * 所有实现该方法处理方法 */ void exceptionHandler(); } Subject:监听者容器 public interface Subject { /** * 添加被观察对象 */ void add(ObServer obServer); /** * 通知所有被观察者完成自己

  • java new一个对象的过程实例解析

    这篇文章主要介绍了java new一个对象的过程实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 java在new一个对象的时候,会先查看对象所属的类有没有被加载到内存,如果没有的话,就会先通过类的全限定名来加载.加载并初始化类完成后,再进行对象的创建工作. 我们先假设是第一次使用该类,这样的话new一个对象就可以分为两个过程:加载并初始化类和创建对象. 一.类加载过程(第一次使用该类) java是使用双亲委派模型来进行类的加载的,所以在

  • Java等待唤醒机制原理实例解析

    这篇文章主要介绍了Java等待唤醒机制原理实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 线程的状态 首先了解一下什么是线程的状态,线程状态就是当线程被创建(new),并且启动(start)后,它不是一启动就进入了执行状态(run),也不是一直都处于执行状态. 这里说一下Java 的Thread类里面有一个State方法,这个方法里面涵盖了6种线程的状态,如下: public enum State { // 尚未启动的线程的线程状态.

  • Java多线程窗口售票问题实例

    本文介绍了多线程实现多个窗口售票问题的两种枷锁方式, 分别是synchronized 和lock()和unlock() 具体代码如下: 第一种: package Runnable; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* * 同步 * 这里有两种方式加锁 * 分别是 * 1.synchronized * 2.lock()和unlock() */ publ

  • Java编程时间日期API实例解析

    本文实例主要是关于Java8中的新特性,时间日期api的相关实例,具体如下: package com.effective.common.base.date; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Period; import java.time.ZoneId; import

随机推荐