原子性(juc编程)

原子性

概述:所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。

//比如说:你喂你女朋友吃冰淇淋,如果没有女朋友,你就假想一下,实在不行,你就喂你旁边的哥们吃一口冰淇淋。这就是一个不可分割的整体,一个是你喂,一个是她吃。这就是一个整体,如果没有她吃,那么你喂就没有意义,如果没有你喂,她吃就没有意义。

//比如:从张三的账户给李四的账户转1000元,这个动作将包含两个基本的操作:从张三的账户扣除1000元,给李四的账户增加1000元。这两个操作必须符合原子性的要求,要么都成功要么

都失败。

4.1 看程序说结果

分析如下程序的执行结果

线程类

public class VolatileAtomicThread implements Runnable {

    // 定义一个int类型的变量
    private int count = 0 ;

    @Override
    public void run() {
        
        // 对该变量进行++操作,100次
        for(int x = 0 ; x < 100 ; x++) {
            count++ ;					
            System.out.println("冰淇淋的个数 =========>>>> " + count);
        }
        
    }

}

测试类

public class VolatileAtomicThreadDemo {

    public static void main(String[] args) {

        // 创建VolatileAtomicThread对象
        VolatileAtomicThread volatileAtomicThread = new VolatileAtomicThread() ;

        // 开启100个线程对count进行++操作
        for(int x = 0 ; x < 100 ; x++) {
            new Thread(volatileAtomicThread).start();
        }
        
    }

}

程序分析:我们在主线程中通过for循环启动了100个线程,每一个线程都会对VolatileAtomicThread类中的count加100次。那么直接结果应该是10000。但是真正的执行结果和我们分析

的是否一样呢?运行程序(多运行几次),查看控制台输出结果

....
count =========>>>> 9997
count =========>>>> 9998
count =========>>>> 9999

通过控制台的输出,我们可以看到最终count的结果可能并不是10000。接下来我们就来分析一下问题产生的原因。

4.2 问题分析说明

以上问题主要是发生在count++操作上:

count++操作包含3个步骤:

  • 从主内存中读取数据到工作内存
  • 对工作内存中的数据进行++操作
  • 将工作内存中的数据写回到主内存

count++操作不是一个原子性操作,也就是说在某一个时刻对某一个操作的执行,有可能被其他的线程打断。

在这里插入图片描述

产生问题的执行流程分析:

  1. 假设此时count的值是100,线程A需要对改变量进行自增1的操作,首先它需要从主内存中读取变量count的值。由于CPU的切换关系,此时CPU的执行权被切换到了B线程。A线程就处

    于就绪状态,B线程处于运行状态。

  2. 线程B也需要从主内存中读取count变量的值,由于线程A没有对count值做任何修改因此此时B读取到的数据还是100

  3. 线程B工作内存中对count执行了+1操作,但是未刷新之主内存中

  4. 此时CPU的执行权切换到了A线程上,由于此时线程B没有将工作内存中的数据刷新到主内存,因此A线程工作内存中的变量值还是100,没有失效。A线程对工作内存中的数据进行了+1操作。

  5. 线程B将101写入到主内存

  6. 线程A将101写入到主内存

虽然计算了2次,但是只对A进行了1次修改。

4.3 volatile原子性测试

我们刚才说到了volatile在多线程环境下只保证了共享变量在多个线程间的可见性,但是不保证原子性。那么接下来我们就来做一个测试。测试的思想,就是使用volatile修饰count。

线程类

public class VolatileAtomicThread implements Runnable {

    // 定义一个int类型的变量,并且使用volatile修饰
    private volatile int count = 0 ;

    @Override
    public void run() {
        
        // 对该变量进行++操作,100次
        for(int x = 0 ; x < 100 ; x++) {
            count++ ;					
            System.out.println("count =========>>>> " + count);
        }
        
    }

}

控制台输出结果(需要运行多次)

...
count =========>>>> 9997
count =========>>>> 9998
count =========>>>> 9999

通过控制台结果的输出,我们可以看到程序还是会出现问题。因此也就证明volatile关键字是不保证原子性的。

4.4 volatile使用场景

volatile关键字不保证原子性操作,那么同学们可能会存在一些疑问,volatile关键字在什么情况下进行使用呢?这里我们举两个基本的使用场景。

4.4.1 状态标志

比如现在存在一个线程不断向控制台输出一段话"传智播客中国IT教育的标杆…",当这个线程执行5秒以后,将该线程结束。

实现思路:定义一个boolean类型的变量,这个变量就相当于一个标志。当这个变量的值为true的时候,线程一直执行,10秒以后我们把这个变量的值更改为false,此时结束该线程的执行。

为了保证一个线程对这个变量的修改,另外一个线程立马可以看到,这个变量就需要通过volatile关键字进行修饰。

线程类

public class VolatileUseThread implements Runnable {

    // 定义标志变量
    private volatile boolean shutdown = false ;

    @Override
    public void run() {

        while(!shutdown) {
            System.out.println("传智播客中国IT教育的标杆....");
        }

    }

    // 关闭线程
    public void shutdown() {
        this.shutdown = true ;
    }

}

测试类

public class VolatileUseThreadDemo01 {

    public static void main(String[] args) throws InterruptedException {

        // 创建线程任务类对象
        VolatileUseThread volatileUseThread = new VolatileUseThread() ;

        // 创建线程对象
        Thread thread = new Thread(volatileUseThread);

        // 启动线程
        thread.start();

        // 主线程休眠
        TimeUnit.SECONDS.sleep(5);

        // 关闭线程
        volatileUseThread.shutdown();

    }

}

观察控制台输出,volatileUseThread线程执行5秒以后程序结束。

4.4.2 独立观察

//AI养猪。。。。

//设备区测量温度

//当温度高了。。。需要给猪开空调。。。加冰棍。。。加喝的水。。。

volatile的另一种简单使用场景是:定期"发布"观察结果供程序内部使用。例如,假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器数据,并更新包

含这个volatile变量的值。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。这种使用就是多个线程操作共享变量,但是是有一个线程对其进行写操作,其他的线程都是读。

我们可以设计一个程序,模拟上面的温度传感器案例。

实现步说明

  1. 定义一个温度传感器(TemperatureSensor)的类,在该类中定义两个成员变量(temperature(温度值),type(传感器的类型)),temperature变量需要被volatile修饰

  2. 定义一个读取温度传感器的线程的任务类(ReadTemperatureRunnable),该类需要定义一个TemperatureSensor类型的成员变量(该线程需要读取温度传感器的数据)

  3. 定义一个定时采集温度的线程任务类(GatherTemperatureRunnable),该类需要定义一个TemperatureSensor类型的成员变量(该线程需要将读到的温度设置给传感器)

  4. 创建测试类(TemperatureSensorDemo)

    1. 创建TemperatureSensor对象
    2. 创建ReadTemperatureRunnable类对象,把TemperatureSensor作为构造方法的参数传递过来
    3. 创建GatherTemperatureRunnable类对象,把TemperatureSensor作为构造方法的参数传递过来
    4. 创建2个Thread对象,并启动,把第二步所创建的对象作为构造方法参数传递过来,这两个线程负责读取TemperatureSensor中的温度数据
    5. 创建1个Thread对象,并启动,把第三步所创建的对象作为构造方法参数传递过来,这个线程负责读取定时采集数据中的温度数据

TemperatureSensor类

public class TemperatureSensor {        // 温度传感器类

    private volatile int temperature ;  // 温度值

    private String type ;               // 传感器的类型

    public int getTemperature() {
        return temperature;
    }

    public void setTemperature(int temperature) {
        this.temperature = temperature;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

ReadTemperatureRunnable类

public class ReadTemperatureRunnable implements Runnable {

    // 温度传感器
    private TemperatureSensor temperatureSensor ;
    public ReadTemperatureRunnable(TemperatureSensor temperatureSensor) {
        this.temperatureSensor = temperatureSensor ;
    }

    @Override
    public void run() {

        // 不断的读取温度传感器中的数据
        while(true) {

            // 读取数据
            System.out.println(Thread.currentThread().getName() + "---读取到的温度数据为------>>> " + temperatureSensor.getTemperature());

            try {
                // 让线程休眠100毫秒,便于观察
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }

}

GatherTemperatureRunnable类

public class GatherTemperatureRunnable implements Runnable {

    // 温度传感器
    private TemperatureSensor temperatureSensor ;
    public GatherTemperatureRunnable(TemperatureSensor temperatureSensor) {
        this.temperatureSensor = temperatureSensor ;
    }

    @Override
    public void run() {

        // 定义一个变量,表示环境初始温度
        int temperature = 23 ;

        // 不断进行数据采集
        while(true) {

            // 将采集到的数据设置给温度传感器
            System.out.println(Thread.currentThread().getName() + "-----采集到的数据为----->>> " + temperature);
            temperatureSensor.setTemperature(temperature);

            try {
                // 线程休眠2秒,模拟每隔两秒采集一次数据
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 环境温度改变
            temperature += 2 ;

        }

    }

}

测试类

public class TemperatureSensorDemo {

    public static void main(String[] args) {

        // 创建TemperatureSensor对象
        TemperatureSensor temperatureSensor = new TemperatureSensor();

        // 创建ReadTemperatureRunnable类对象
        ReadTemperatureRunnable readTemperatureRunnable = new ReadTemperatureRunnable(temperatureSensor) ;

        // 创建GatherTemperatureRunnable类对象
        GatherTemperatureRunnable gatherTemperatureRunnable = new GatherTemperatureRunnable(temperatureSensor) ;

        // 创建2个Thread对象,并启动; 这两个线程负责读取TemperatureSensor中的温度数据
        for(int x = 0 ; x < 2 ; x++) {
            new Thread(readTemperatureRunnable).start();
        }

        // 创建1个Thread对象,并启动,这个线程负责读取定时采集数据中的温度数据
        Thread gatherThread = new Thread(gatherTemperatureRunnable);
        gatherThread.setName("温度采集线程");
        gatherThread.start();

    }

}

控制台输出结果

...
温度采集线程-----采集到的数据为----->>> 23
Thread-0---读取到的温度数据为------>>> 23
...
温度采集线程-----采集到的数据为----->>> 25
Thread-1---读取到的温度数据为------>>> 25
...

通过控制台的输出,我们可以看到当温度采集线程刚采集到环境温度以后,那么此时两个温度读取线程就可以立即感知到环境温度的变化。

4.5 问题处理

接下来我们就来讲解一下我们上述案例(引入原子性问题的案例)的解决方案。

4.5.1 锁机制

我们可以给count++操作添加锁,那么count++操作就是临界区中的代码,临界区中的代码一次只能被一个线程去执行,所以count++就变成了原子操作。

线程任务类

public class VolatileAtomicThread implements Runnable {

    // 定义一个int类型的变量,
    private int count = 0 ;

    // 定义一个Object类型的变量,该变量将作为同步代码块的锁
    private Object obj = new Object();

    @Override
    public void run() {
        
        // 对该变量进行++操作,100次
        for(int x = 0 ; x < 100 ; x++) {
            synchronized (obj){
                count++ ;
                System.out.println("count =========>>>> " + count);
            }

        }
        
    }

}

控制台输出结果

count =========>>>> 9998
count =========>>>> 9999
count =========>>>> 10000

4.5.2 原子类

1) AtomicInteger

概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。因为变

量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。本次我们只讲解

使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:

AtomicBoolean: 原子更新布尔类型

AtomicInteger: 原子更新整型

AtomicLong: 原子更新长整型

以上3个类提供的方法几乎一模一样,所以本节仅以AtomicInteger为例进行讲解,AtomicInteger的常用方法如下:

public AtomicInteger():	   				初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer

int get():   			 				 获取值
int getAndIncrement():      			 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet():     				 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data):				 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value):   			 以原子方式设置为newValue的值,并返回旧值。

案例演示AtomicInteger的基本使用:

public class AtomicIntegerDemo01 {

    // 原子型Integer
    public static void main(String[] args) {

        // 构造方法
        // public AtomicInteger():初始化一个默认值为0的原子型Integer
        // AtomicInteger atomicInteger = new AtomicInteger() ;
        // System.out.println(atomicInteger);

        // public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
        AtomicInteger atomicInteger = new AtomicInteger(5) ;
        System.out.println(atomicInteger);

        // 获取值
        System.out.println(atomicInteger.get());

        // 以原子方式将当前值加1,这里返回的是自增前的值
        System.out.println(atomicInteger.getAndIncrement());
        System.out.println(atomicInteger.get());

        // 以原子方式将当前值加1,这里返回的是自增后的值
        System.out.println(atomicInteger.incrementAndGet());

        // 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
        System.out.println(atomicInteger.addAndGet(8));

        // 以原子方式设置为newValue的值,并返回旧值
        System.out.println(atomicInteger.getAndSet(20));
        System.out.println(atomicInteger.get());

    }

}
2) 案例改造

使用AtomicInteger对案例进行改造。

public class VolatileAtomicThread implements Runnable {

    // 定义一个int类型的变量
    private AtomicInteger atomicInteger = new AtomicInteger() ;

    @Override
    public void run() {

        // 对该变量进行++操作,100次
        for(int x = 0 ; x < 100 ; x++) {
            int i = atomicInteger.incrementAndGet();
            System.out.println("count =========>>>> " + i);
        }

    }

}

控制台输出结果

...
count =========>>>> 9998
count =========>>>> 9999
count =========>>>> 10000

通过控制台的执行结果,我们可以看到最终得到的结果就是10000,因此也就证明AtomicInteger所提供的方法是原子性操作方法。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SOFTS: Efficient Multivariate Time Series Forecasting with Series-Core Fusion

SOFTS: Efficient Multivariate Time Series Forecasting with Series-Core Fusion 文章目录 SOFTS: Efficient Multivariate Time Series Forecasting with Series-Core Fusion1. 论文背景1.1 通道独立-通道依赖的区别1.2 论文贡献 2. 模型架构2.1 SOFTS的主要架构2.2 STAR 模…

《QT实用小工具·七十一》基于Qt+Qml开发的文件传输工具

1、概述 源码放在文章末尾 该项目基于QTQML实现了文件传输的功能&#xff0c;可以在局域网环境下使用(热点)&#xff0c;扫描使用UDP&#xff0c;传输使用TCP&#xff0c;每一个文件传输使用独立的线程进行处理&#xff0c;高效便捷。 开发环境 使用Qt/Qml开发 QT版本&#x…

搭建开发模式下的以太坊私有链【Geth:1.14.5】

一、为什么用到私有链&#xff1f; 在以太坊的公有链上部署智能合约、发起交易需要花费以太币。而通过修改配置&#xff0c;可以在本机搭建一套以太坊私有链&#xff0c;因为与公有链没关系&#xff0c;既不用同步公有链庞大的数据&#xff0c;也不用花钱购买以太币&#xff0…

Mybatis中BaseEntity作用

新建各种对象的时候&#xff0c;一般来说&#xff0c;有几个属性是所有对象共有的&#xff0c;比如说id,is_del&#xff0c;is_enable这些&#xff0c;然后设置一个基础对象&#xff0c;以后新建所有对象的时候都继承它&#xff0c;就省的每次都要写这些共有的属性了

生育是家庭和个人的重要的选择

生育是个人和家庭的重要选择&#xff0c;而国家对于生育政策的制定应该综合考虑多种因素&#xff0c;包括人口结构、经济发展和社会稳定等。同时&#xff0c;我们也应该认识到&#xff0c;男女不应该成为决定一个人是否能够生育的因素。男女在生育中扮演着不同的角色&#xff0…

在Maven工程中手动配置并测试SpringBoot(巨详)

本篇博客承继自博客&#xff1a; 在IDEA 2024.1.3 (Community Edition)中创建Maven项目_idea2024.1.3如何创建maven项目-CSDN博客 配置POM文件 打开工程中的pom.xml文件&#xff0c;先向其中写入 <parent><groupId>org.springframework.boot</groupId><…

【总线】AXI4第三课时:握手机制

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣&#xff0c;那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者&#xff0c;AXI4以其高性能和高度可扩展性&#xff0c;成为了现代电子系统中不可或缺的通信桥梁…

Java面向对象的三大特性之一——继承

目录 一、继承概念 二、为什么要继承 三、继承语法&#xff08;关键字extends&#xff09; 四、父类成员访问 1、子类中访问父类的成员变量 &#xff08;1&#xff09;子类和父类不存在同名的成员变量 &#xff08;2&#xff09;子类和父类中存在同名的成员变量 2、子类中访…

Transformer学习理解

1.前言 本文介绍当下人工智能领域的基石与核心结构模型——Transformer&#xff0c;为什么说它是基石&#xff0c;因为以ChatGPT为代表的聊 天机器人以及各种有望通向AGI&#xff08;通用人工智能&#xff09;的道路上均在采用的Transformer。 Transformer也是当下NLP任…

基于PCL实现多边形框选点云并进行裁剪(附C++源码)

文章目录 一.算法效果二.算法原理PNPoly算法直线相交性判断三.代码实现一.算法效果 通过在PCL可视化界面上绘制2D封闭多边形来提取位于该封闭多边形内部或者外部的 的点,算法效果如下: 图1多边形裁剪点云效果图 二.算法原理 PNPoly算法 2D多边形框选裁剪点云,实际上可以简…

SSRF学习,刷题

[HNCTF 2022 WEEK2]ez_ssrf 给了一个Apache2的界面&#xff0c;翻译一下 就是一个默认的界面,目录扫描 可以看到flag.php,肯定是不能直接访问得到的&#xff0c;还有index.php&#xff0c;访问这个 可以看到三个参数data,host,port 还有fsockopen() 函数是 PHP 中用于打开一个…

代码随想录训练营Day 64|卡码网98. 所有可达路径(深搜)

1.所有可达路径 98. 所有可达路径 | 代码随想录 代码&#xff1a; &#xff08;深搜&#xff09;邻接矩阵表示 #include <iostream> #include <vector> using namespace std; vector<int> path; vector<vector<int>> result; void dfs(const ve…

LeetCode 算法:两两交换链表中的节点 c++

原题链接&#x1f517;&#xff1a;两两交换链表中的节点 难度&#xff1a;中等⭐️⭐️ 题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交…

QT中利用动画弄一个侧边栏窗口,以及贴条效果

1、效果 2、关键代码 void Widget::on_sliderBtn_clicked() {m_sliderWidget->show();QPropertyAnimation* animation = new QPropertyAnimation(m

政策更新记录:敏感信息访问权限与API使用变更

我们将更新“健康数据共享”政策,简化“健康数据共享”申请流程,并与“健康类应用”政策保持一致。此外,我们将于今年晚些时候在 Play 管理中心推出一项新的声明,取代当前使用表单进行申请的方式。 公布日期:2024-04-03 Health Connect 政策要求及常见问题解答 初步认识对…

[AIGC] 使用Google的Guava库中的Lists工具类:常见用法详解

在Java程序设计中&#xff0c;集合是我们最常用的数据结构之一。为了方便我们操作集合&#xff0c;Google的Guava库提供了一个名为Lists的工具类&#xff0c;它封装了许多用于操作List对象的实用方法。在本文中&#xff0c;我们将详细介绍其常见的用法&#xff0c;以帮助您更好…

volatile关键字(juc编程)

volatile关键字 3.1 看程序说结果 分析如下程序&#xff0c;说出在控制台的输出结果。 Thread的子类 public class VolatileThread extends Thread {// 定义成员变量private boolean flag false ;public boolean isFlag() { return flag;}Overridepublic void run() {// 线…

钡铼BL101网关助力智慧城市路灯远程智能管控

在迈向智慧城市的征途中&#xff0c;基础设施的智能化改造是关键一环&#xff0c;而路灯作为城市脉络的照明灯塔&#xff0c;其智能化升级对于节能减排、提升城市管理效率具有重要意义。钡铼BL101网关&#xff0c;作为Modbus转MQTT的专业桥梁&#xff0c;正以其卓越的性能和广泛…

数据仓库与数据库的区别

在数据管理和分析的过程中&#xff0c;我们常常会听到“数据库”和“数据仓库”这两个术语。 虽然它们看起来相似&#xff0c;但实际上它们在设计目的、结构和使用场景上都有显著的区别。 数据库是什么&#xff1f; 数据库&#xff08;Database&#xff09;是一个用于存储和管…

[创业之路-120] :全程图解:软件研发人员如何从企业的顶层看软件产品研发?

目录 一、企业全局 二、供应链 三、团队管理 四、研发流程IPD 五、软件开发流程 六、项目管理 七、研发管理者的自我修炼 一、企业全局 二、供应链 三、团队管理 四、研发流程IPD 五、软件开发流程 六、项目管理 七、研发管理者的自我修炼