Java 多线程共享数据引发的问题

news2024/10/7 12:18:37

一、多线程并发情况下,线程不安全​​

1、使用多线程实现银行取钱​​

package theads;

/**
 * @ClassName: TestBank
 * @Description: TODO
 * @Author: HLX
 * @date: 2023/5/29 14:53
 * @Version: V1.0
 */

/**
 * 线程不安全: 取钱
 * <p>
 * 逻辑:
 * 连取两次  则为负数 -70;
 * 各自的口袋的钱是没问题的。
 */
public class TestBank {
    public static void main(String[] args) {
        //账户上有100元生活费用
        Account account = new Account(100, "生活费用");
        System.out.println(account);

        //线程
        MyBank t1 = new MyBank("张三", account, 80);
        MyBank t2 = new MyBank("刘芳", account, 90);
        MyBank t3 = new MyBank("汪峰", account, 50);

        t1.start();
        t2.start();
        t3.start();
    }
}

//银行账户
class Account {
    private int money; //金额
    private String name; //备注

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }

    @Override
    public String toString() {
        return name + "入账" + money + "元";
    }

    public Account() {
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//线程
class MyBank extends Thread {
    private Account account; //账户
    private int takeMoney;//取钱

    //构造方法
    public MyBank(String name, Account account, int takeMoney) {
        super(name);
        this.account = account;
        this.takeMoney = takeMoney;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);         // 模拟延迟网络时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //从账户上取取钱
        if (account.getMoney() - takeMoney < 0) {
            return;
        }
        try {
            Thread.sleep(1000);         // 如果不堵塞的话,速度太快。看不到问题
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        设置账户上剩余的钱
        account.setMoney(account.getMoney() - takeMoney);
        System.out.println(this.getName() + "取钱:" + takeMoney + "元,余额为:" + account.getMoney() + "元");

    }
}

从运行的效果来看:
余额竟然会出现-40的情况,原因是有100现金,
汪峰取50,剩-40
刘芳取90,剩-40
张三取80,剩-40
也就相当于那个判断没起作用。这就是线程不安全。

 原因是:3个进程同时操作这个唯一的资源,就会出现线程不安全的情况。

 二、线程不安全解密:内存可见性

 1、 java内存模型

   (1)Java的内存模型分为主内存工作内存(线程的)

   (2)Java内存模型规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。

   (3)线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。有一个关键字是 ​​volatile​​, volatile可以保证任何情况下变量的可见性,是不是就是直接读主内存呢,事实上,volatile变量依然有工作内存的拷贝,但是它的操作顺序比较特殊,会每次都从主内存重新加载,所以你会看到每次volatile读取到的都是最新的值。

   (4)不同的线程也无法访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来完成。

2、线程不安全说明:

      当多个线程操作同一个对象或者同一个资源时(如取钱),每个线程会把资源从java主内存中 读取一份到自己的工作内存中,进行操作,操作后,在将结果写到java主内存中。但是,因为有多个线程在同时操作,就要多个工作内存去读和写,假如有A,B 两个进程,都读取了一份资源到自己的工作内存中, B内存可能没被更新到主内存去。导致A线程或者其他内存 从主内存拷贝数据到自己的工作区时,拷贝的不是最新的数据。这就是 ​​内存可见性问题​​。从而导致线程不安全问题。
 

3、线程安全与不安全

     线程不安全:是不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是​​脏数据​​

      线程安全:指多个线程在执行同一段代码的时候采用​​加锁机制​​,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。
 

三、解决线程安全性问题

    1、 线程同步概念

        当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线      程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。

     2、使用线程同步方法和同步块

    (1) 同步方法语法

访问修饰符 synchronized 返回类型 方法名(参数列表){……}

or

synchronized 访问修饰符 返回类型 方法名(参数列表){……}

   (2) 同步块语法

   synchronized (obj){    },  // obj可以是任何对象,但是推荐使用共享资源作为同步监视器

  使用多线程同步块实现银行取钱​​

package theads;

/**
 * @ClassName: TestBank
 * @Description: TODO
 * @Author: HLX
 * @date: 2023/5/29 14:53
 * @Version: V1.0
 */

public class TestBank2 {
    public static void main(String[] args) {
        //账户上有200元生活费用
        Account2 account2 = new Account2(200, "生活费用");
        System.out.println(account2);

        MyBank2 t1 = new MyBank2("张三", account2, 80);
        MyBank2 t2 = new MyBank2("刘芳", account2, 90);
        MyBank2 t3 = new MyBank2("汪峰", account2, 10);

        t1.start();
        t2.start();
        t3.start();
    }
}

//银行账户
class Account2 {
    private int money; //金额
    private String name; //备注

    public Account2(int money, String name) {
        this.money = money;
        this.name = name;
    }

    @Override
    public String toString() {
        return name + "入账" + money + "元";
    }

    public Account2() {
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//线程
class MyBank2 extends Thread {
    private Account2 account; //账户
    private int takeMoney;//取钱

    //构造方法
    public MyBank2(String name, Account2 account, int takeMoney) {
        super(name);
        this.account = account;
        this.takeMoney = takeMoney;
    }

    //缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
    @Override
    public void run() {
        /**
         * 提高性能
         */
        if (account.getMoney() <= 0) {
            return;
        }
        //目标锁定account账户
        //同步块
        synchronized (account) {
            //从账户上取取钱
            if (account.getMoney() - takeMoney < 0) {
                return;
            }
            try {
                Thread.sleep(1000);         // 模拟网络延时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
//        设置账户上剩余的钱
            account.setMoney(account.getMoney() - takeMoney);
            System.out.println(this.getName() + "取钱:" + takeMoney + "元,|__余额为:" + account.getMoney() + "元");

        }
    }
}

  • 同一时刻只能有一个线程进入synchronized(this)同步代码块
  • 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
  • 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码 

 

 四、synchronized 的范围问题,优化效率

(1) 同步方法 所锁住的范围太大,线程安全,影响效率。
(2) 同步块 所锁住的范围太小,锁不住,线程不安全。

 

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

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

相关文章

更改测试用例执行顺序的几种自动化方法

前言 在自动化测试中&#xff0c;自动化测试用例设计原则就是执行过程时不能存在依赖顺序&#xff0c;那么如果测试用例需要按照指定顺序执行&#xff0c;这个时候应该怎么做呢&#xff1f;目前单元测试框架中unittest没有办法改变测试用例的执行顺序&#xff0c;但是另一个单…

一顿操作,我成为了年薪40W+的测试开发,人麻了...

前情提要 我的第一份工作就是拿的8000多&#xff0c;主要以功能测试为主。我用了大概6年的时间&#xff0c;成为了年薪40W的测试开发。回顾我从功能测试到测试开发的成长路径&#xff0c;基本上是伴随着“3次能力飞跃”实现的。 第一家入职的时候是一家小公司 刚开始入行的时…

发改委强化电力需求侧管理,缓解电力系统峰值压力

安科瑞 耿敏花 摘要&#xff1a;近年来全国用电负荷特别是居民用电负荷的快速增长&#xff0c;全国范围内夏季、冬季用电负荷“双峰”特征日益突出&#xff0c;恶劣气候现象多发增加了电力安全供应的压力。具有随机性、波动性、间歇性特征的可再生能源大规模接入电网对电力系统…

WebrtcNode publish 流程

WebrtcNode publish 流程 1. AmqpClient - RpcServer New message received AmqpClient - RpcServer New message received {method: publish,args: [67f9309ce6e645fc8a4bb9cac6406eb2,webrtc,{transportId: 67f9309ce6e645fc8a4bb9cac6406eb2,tracks: [Array],controller: …

Spring注解开发——bean的作用范围与生命周期管理

文章目录 1.bean管理1.1 bean作用范围Scope注解 1.2 bean生命周期PostConstructPreDestroy 2.小结 1.bean管理 1.1 bean作用范围 Scope注解 不写或者添加Scope(“singleton”)表示的是单例 如何配置多例&#xff1f; 在Scope(“prototype”)表示的是多例 1.2 bean生命周…

chatgpt赋能python:Python中的倒序函数

Python中的倒序函数 Python是一种现代编程语言&#xff0c;它在不断地扩展和更新&#xff0c;使得它在编程领域中变得越来越流行。Python的一个特点是&#xff0c;代码简洁而且易于理解。其中一个重要的特性是它的倒序函数。在本文中&#xff0c;我们将介绍Python中的倒序函数…

一位年薪40W的测试被开除,回怼的一番话,令人沉思

一位年薪40W测试工程师被开除回怼道&#xff1a;“反正我有技术&#xff0c;在哪不一样” 一技傍身&#xff0c;万事不愁&#xff0c;当我们掌握了一技之长后&#xff0c;在职场上说话就硬气了许多&#xff0c;不用担心被炒&#xff0c;反过来还可以炒了老板&#xff0c;这一点…

基于深度学习的人脸识别与人员信息管理软件【python源码+UI界面+功能源码详解】

人脸识别功能演示 摘要&#xff1a;人脸识别&#xff08;Face Recognition&#xff09;是基于人的脸部特征信息进行身份识别的一种生物识别技术&#xff0c;可以用来确认用户身份。本文详细介绍了人脸识别基本的实现原理&#xff0c;并且基于python与pyqt开发了人脸识别与信息管…

智哪儿专访IF ROOM品牌主理人Amy:IF ROOM如何定义未来家居?

作为未来主义家居首倡者&#xff0c;IF ROOM在5月25日-28日举行的深圳时尚家居设计周上首次亮相就获得行业广泛热议。IF ROOM展馆以其未来主义设计风格和跨越东西方文化的美的意象&#xff0c;得到了包括设计师在内的广大行业人士追捧&#xff0c;IF ROOM展位成为深圳时尚家居设…

​字创未来 方正字库第十二届“方正奖”设计大赛正式来袭

传承汉字文化精髓&#xff0c;方正字库在字体行业不断探索深耕。方正字库一直致力于弘扬中华汉字文化&#xff0c;不断促进行业字体设计创新发展。于2001年在行业最艰难的时候&#xff0c;怀揣着对字体设计未来的美好向往&#xff0c;首届“北大方正奖”印刷字体设计大赛&#…

灵活使用Postman环境变量和全局变量,提高接口测试效率!

目录 前言&#xff1a; 环境变量和全局变量的概念 环境变量和全局变量的使用方法 1. 定义变量 2. 使用变量 环境变量和全局变量的实例代码 变量的继承和覆盖 变量的动态设置 总结&#xff1a; 前言&#xff1a; Postman是一个流行的API开发和接口测试工具&#xff0c;…

过滤器Filter,拦截器Interceptor

过滤器Filter 快速入门 详情 登录校验-Filter package com.itheima.filter;import com.alibaba.fastjson.JSONObject; import com.itheima.pojo.Result; import com.itheima.utils.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils…

码住!IC设计常用工具合集!

芯片设计过程中&#xff0c;选择和使用适合的工具是非常重要的。芯片设计工具通常分为三类&#xff1a;EDA工具、模拟仿真工具和布局工具。 一、EDA工具 EDA工具是芯片设计的核心&#xff0c;它包括原理图绘制、逻辑综合、门级仿真工具和物理版图编辑等&#xff0c;可以帮助设计…

Ubuntu 安装 jdk1.8

1 更新软件包 sudo apt update 2 安装jdk1.8 sudo apt install openjdk-8-jdk 3 查看版本 java -version安装成功

自适应滤波方法——LMS算法

自适应滤波器 自适应滤波器&#xff1a;一种能够根据输入信号自动调整自身参数的数字滤波器 非自适应滤波器&#xff1a;具有静态滤波器系统的数字滤波器&#xff0c;静态系数构成了滤波器的传递函数 对于一些应用&#xff08;如系统辨识、预测、去噪等&#xff09;&#xff…

固定翼无人机1:500地籍

引言 上几期&#xff0c;睿铂为大家推送了两篇相对精度验证的文章&#xff0c;其优秀的成果引来了业界人士不少的赞叹。同时&#xff0c;许多客户朋友又提出了更高的要求。目前&#xff0c;在地籍精度的项目中&#xff0c;使用多旋翼无人机挂载倾斜摄影相机作业&#xff0c;是能…

机器学习笔记 - 基于TensorFlow Lite的模型部署

一、简述 TensorFlow Lite 是一个移动端库,可用于在移动设备、微控制器和其他边缘设备上部署模型。 假设要执行图像分类任务。首先决定任务的模型。是要创建自定义模型;或者使用预训练模型,如 InceptionNet、MobileNet、NASNetLarge 等。又或者在预训练模型上应用迁…

【问题记录】解决vite多页应用路由改用history之后本地刷新404问题

当前包的版本信息&#xff1a; "vue": "^2.7.14", "vue-router": "^3.6.5" "vite": "^3.0.7", 首先&#xff0c;修改路由模式 首先&#xff0c;将之前多页项目中的某个页面路由模式改用 history &#xff0c;…

C++服务器框架开发4——日志系统logger/.cpp与.cc

该专栏记录了在学习一个开发项目的过程中遇到的疑惑和问题。 其教学视频见&#xff1a;[C高级教程]从零开始开发服务器框架(sylar) 上一篇&#xff1a;C服务器框架开发3——协程与线程的简单理解/并发与并行 C服务器框架开发4——日志系统logger 目前进度.cpp与.cc 目前进度 …