首页 > 其他分享 >CAS & volatile

CAS & volatile

时间:2023-04-05 20:44:08浏览次数:50  
标签:变量 CAS flag 线程 内存 volatile

1. CAS

1.1 问题

在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁
锁机制存在以下问题:
(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。
(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

1.2 解决

CAS:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS,Compare and Swap。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。
通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。

1.3 CAS的目的

利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。

1.4 CAS的问题

(1)ABA问题:被监控的值变化:A->B->A,而再次成为A的时候,通常程序无法发现。
解决:加入版本控制:A1->B2->A3
(2)循环时间开销
解决:如果JVM能支持处理器提供的pause指令那么效率会有一定的提升
(3)只能保证一个共享变量的原子操作
解决:把多个变量合并一同操作:i=2 & j=a -> ij=2a

2. volatite

——内存锁定,同一时刻只有一个线程可以修改内存值

2.1 变量不可见性

——多线程下变量不可见性
在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改了共享变量的值后,另一个线程不能直接看到该线程修改后的变量的最新值的情况。

public class Volatile_ {
    public static void main(String[] args) {
        MyThread_ thread_ = new MyThread_();
        thread_.start();
        while (true) {
            synchronized (thread_) {
                if (thread_.isFlag())
                    System.out.println("true+");
            }
        }
    }
}

class MyThread_ extends Thread {
    private boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        flag = true;
        System.out.println(flag);
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

(1) JMM & 不可见原理

——Java Memory Model

image.png

主内存:各个线程的共享变量;工作内容:单个线程的共享变量副本。
JMM有以下规定:
● 所有的共享变量都存储于主内存。这里所说的变量指的是实例变量和类变量。不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题
● 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本
● 线程对变是的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量
● 不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成
过程:
● 在程序加载时,flag被加载到了主内存中
● 在创建线程时,有一份flag被拷贝到了其工作内存中
image.png
可见性问题的原因:
所有共享变量存在于主内存中,每个线程由自己的本地内存,而且线程读写共享数据也是通过本地内存交换的,所以才导致了可见性问题。

(2)解决

——加锁 & volatile

synchronized (thread_) {
    if (thread_.isFlag())
        System.out.println("true+");
}

原理:某一个线程进入synchronized代码块前后,执行过程入如下:
a. 线程获得锁
b. 清空工作内存
c. 从主内存拷贝共享变量最新的值到工作内存成为副本
d. 执行代码
e. 将修改后的副本的值刷新回主内存中
f. 线程释放锁
private volatile boolean flag = false;
原理:
volatile对变量进行修改之后,会通知到其他线程所对应的变量失效,需要更新。
image.png
volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。

2.2 volatile,不保证原子性

场景:有变量=0;创建100个线程,每个线程对变量做1000次+1操作,最终的结果<100000

public class Volatile_ {
    public static void main(String[] args) {
        Runnable target = new Thread_M();
        for (int i = 0; i < 1000; i++) {
            new Thread(target).start();
        }
    }
}

class Thread_M implements Runnable {
    private volatile int count = 0;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            count++;
            System.out.println(count);
        }
    }
}

原因
image.png
volatile只是保证了多线程下变量修改的可见性,而并不保证多线程对变量修改的安全性。

(1)解决一:加锁

加锁:count++这个操作并不具有原子性,加锁保证其原子性

@Override
public void run() {
    synchronized(ThreadM_.class) {
        // 将当前任务类的字节码文件作为锁对象
        for (int i = 0; i < 100; i++) {
            count++;
            System.out.println(count);
        }
    }
}

(2)方法二:原子类-CAS

iava从DK1.5开始提供了ava.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了-种用法简单,性能高效,线程安全地更新一个变量的方式。
image.png
image.png
:“本地方法”

2.5 volatile:禁止指令重排序

什么是重排序:为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序.
原因:一个好的内存模型实际上会放松对处理器和编译器规则的束缚,也就是说软件技术和硬件技术都为同一个目标而进行奋斗:在不改变程序执行结果的前提下,尽可能提高执行效率。JMM对底层尽量减少约束,使其能够发挥自身优势。因此,在执行程序时,为了提高性能,编译器和处理器常常会对指令进行重排序。一般重排序可以分为如下三种:
● 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序:
● 指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序;
● 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行的。
简单来讲:JVM上层程序执行和JVM底层依赖机器指令等合作执行可以提高效率,在不改变结果的情况下,一般都会支持指令重排序以获得较好的性能。
image.png
问题:并发执行下,JVM虚拟机并不能保证指令重排序带来的安全性问题。
场景:在交叉赋值的情况下,由于CPU的指令重排序特性,可能出现变量赋值顺序颠倒的情况。
解决:使用volatile修饰变量禁止重排序。


volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。
如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

  1. 首先,声明共享变量为volatile;
  2. 然后,使用CAS的原子条件更新来实现线程之间的同步;
  3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
    volatile保证了变量的内存可见性,也就是所有工作线程中同一时刻都可以得到一致的值。

标签:变量,CAS,flag,线程,内存,volatile
From: https://www.cnblogs.com/YuanShiRenY/p/17290847.html

相关文章

  • 时序预测Time Series Forecasting:实体店销售
    ​1.探索性数据分析:在这个时间序列的"入门"比赛中,我们被要求预测来自CorporaciónFavorita的商店销售数据,这是一家位于厄瓜多尔的大型杂货零售商。我们需要一个能够预测不同商店所销售的数千种商品的单位销售额的模型。在这次比赛中,我们有不同的数据集,描述了厄瓜多尔2013年......
  • CAS-并发
    CAS(Compareandswap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。一、前言  了解了一下JDK1.8中ConcurrentHashMap的实现,发现它实现的主要......
  • case的穿透优化
    importjava.util.Scanner;publicclasspenetrate{publicstaticvoidmain(String[]args){}publicstaticvoidswitchTest1(){//键盘录入一个数值,其中1-5表示工作日,6-7表示休息日Scannersc=newScanner(System.in);System.out......
  • Cassandra一个节点到底应该存放多大数据
    在Cassandra2.x版本及更早版本的时候,我经常建议用户单节点规模数据不要超过1T,到Cassandra3.x之后我又建议用户单节点规模不要超过4T。为什么会有这些变化,其实是跟基础设施的发展有关系的。一方面是随着SSD硬盘的越来越廉价,大部分用户使用SSD替换了机械硬盘提升了磁盘随机读写能......
  • AirCassette音乐应用:复古情愫与现代社交元素的完美融合
    随着iPod和iPhone等现代设备的涌现,音乐已变得无处不在。在享受数字音乐带来的轻松体验时,是否也会偶尔怀念那个老式随身听和磁带的年代?AirCassette就是这样一款融合了怀旧情感和现代社交元素的iOS音乐应用。通过这款时尚的应用,用户可在播放数字音乐时体验磁带带来的视觉享受,同时......
  • Cassandra 数据模型
    Cassandra数据模型Cassandra的数据模型与我们通常在关系型数据库中看到的有很大不同。本文概述了Cassandra如何存储其数据。ClusterCassandra数据库分布在多台一起运行的机器上。最外面的容器称为集群。对于故障处理,每个节点都包含一个副本,如果发生故障,副本将负责。Cassa......
  • MYSQL中CAST函数
    MYSQL中CAST函数CAST函数用于将值从一种转换数据类型的方法语法:SELECTCAST(xAStype);x:要处理的数据type:要转换的数据类型,取值下方表格type取值值描述DATE将value转换成'YYYY-MM-DD'格式TIME将value转换成'HH:MM:SS'格式DATETIME将value转换......
  • CAS
    CAS的安装与配置CAS(CentralAuthenticationService)是Yale大学发起的构建WebSSO的Java开源项目。iServer、iPortal、iEdge支持基于CAS的单点登录。用户配置单点登录时,需设置CAS认证服务器,CAS认证服务器负责完成对用户信息的认定,可单独部署于网络环境中。在使用C......
  • 【Java 并发】【五】volatile怎么通过内存屏障保证可见性和有序性
    1 前言这节我们就来看看volatile怎么通过内存屏障保证可见性和有序性。2  保证可见性volatile修饰的变量,在每个读操作(load操作)之前都加上Load屏障,强制从主内存读取最新的数据。每次在assign赋值后面,加上Store屏障,强制将数据刷新到主内存。以volatileintx=0;线程A、B进行......
  • mutable、const、volatile关键字
    C++中有三种修饰数据可变的关键字:mutable、const、volatile。constconst我们很常见,在定义一些不可变的常量或不修改数据内容的函数时经常会用到。 修饰变量,说明该变量不可以被改变;修饰指针,分为指向常量的指针(例如constchar*,其自身可变,指向的是常量字符数组)和自身是常量的......