JavaWeb8-线程安全问题

news2024/9/23 19:25:18

目录

1.概念

1.1.单线程

1.2.多线程

2.导致线程不安全的5个因素

①抢占式执行(首要原因)

②多个线程同时修改了同一个变量

③非原子性操作

④内存可见性

⑤指令重排序


  • 线程优点:加速程序性能。
  • 线程缺点:存在安全问题。

1.概念

线程不安全指的是程序在多线程的执行结果不符合预期。 例如:

1.1.单线程

public class ThreadDemo17 {
    static class Counter{
        //变量
        private int number = 0;

        //循环次数
        private int MAX_COUNT = 0;
        public Counter(int MAX_COUNT) {
            this.MAX_COUNT = MAX_COUNT;
        }

        //++方法
        public void incr() {
            for (int i = 0; i < MAX_COUNT; i++) {
                number++;
            }
        }

        //--方法
        public void decr() {
            for (int i = 0; i < MAX_COUNT; i++) {
                number--;
            }
        }

        public int getNumber() {
            return number;
        }
    }

    public static void main(String[] args) {
        Counter counter = new Counter(100000);
        //++操作
        counter.incr();

        //--操作
        counter.decr();

        //打印结果
        System.out.println("最终结果:" + counter.getNumber());
    }
}

1.2.多线程

public class ThreadDemo17 {
    static class Counter{
        //变量
        private int number = 0;

        //循环次数
        private int MAX_COUNT = 0;
        public Counter(int MAX_COUNT) {
            this.MAX_COUNT = MAX_COUNT;
        }

        //++方法
        public void incr() {
            for (int i = 0; i < MAX_COUNT; i++) {
                number++;
            }
        }

        //--方法
        public void decr() {
            for (int i = 0; i < MAX_COUNT; i++) {
                number--;
            }
        }

        public int getNumber() {
            return number;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter(100000);

        Thread t1 = new Thread(() -> {
            //++操作
            counter.incr();
        });

        Thread t2 = new Thread(() -> {
            //--操作
            counter.decr();
        });

        //启动多线程进行执行
        t1.start();
        t2.start();

        //等待两个线程执行完
        t1.join();
        t2.join();

        //打印结果
        System.out.println("最终结果:" + counter.getNumber());
    }
}

每次执行结果都不同。这就是线程不安全。

2.导致线程不安全的5个因素

①抢占式执行(首要原因)

由于CPU资源较少,而线程较多,狼多肉少,发生争抢混乱。

若将上面代码改为串行执行:线程1执行完之后,线程2再执行(相当于单线程效率),就不会争抢。

public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter(100000);

        Thread t1 = new Thread(() -> {
            //++操作
            counter.incr();
        });
        t1.start();
        t1.join();

        Thread t2 = new Thread(() -> {
            //--操作
            counter.decr();
        });
        t2.start();
        t2.join();

        System.out.println("最终结果:" + counter.getNumber());
    }

②多个线程同时修改了同一个变量

改动代码,让不同线程各自修改各自的变量,就ok了。

public class ThreadDemo17 {
    static class Counter{
        //变量
        private int number = 0;

        //循环次数
        private int MAX_COUNT = 0;
        public Counter(int MAX_COUNT) {
            this.MAX_COUNT = MAX_COUNT;
        }

        //++方法
        public int incr() {
            int temp = 0;
            for (int i = 0; i < MAX_COUNT; i++) {
                temp++;
            }
            return temp;
        }

        //--方法
        public int decr() {
            int temp = 0;
            for (int i = 0; i < MAX_COUNT; i++) {
                temp--;
            }
            return temp;
        }

        public int getNumber() {
            return number;
        }
    }

    static int num1 = 0;
    static int num2 = 0;

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter(100000);

        Thread t1 = new Thread(() -> {
            //++操作
            num1 = counter.incr();
        });

        Thread t2 = new Thread(() -> {
            //--操作
            num2 = counter.decr();
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("最终结果:" + (num1 + num2));
    }
}

③非原子性操作

什么是原子性? ——将一组操作封装成一个执行单元,要一次性执行完,中间不能停顿。

一条 java 语句不一定是原子的,也不一定只是一条指令。

⽐如刚才的 n++,其实是由三步操作组成的:

  1. 从内存把数据读到 CPU
  2. 进⾏数据更新
  3. 把数据写回到 CPU

不保证原子性会给多线程带来什么问题?

如果⼀个线程正在对⼀个变量操作,中途其他线程插⼊进来了,如果这个操作被打断了,结果就可能是错误的。

这点也和线程的抢占式调度密切相关. 如果线程不是 "抢占" 的, 就算没有原⼦性, 也问题不⼤。

例如:

把⼀段代码想象成⼀个房间,每个线程就是要进⼊这个房间的⼈。如果没有任何机制保证,A进⼊房间后还没出来,B 也进⼊房间,打断 A 在房间⾥的隐私。这就是不具备原⼦性的。

如何解决?

给房间加⼀把锁,A 进去就把⻔锁上,其他⼈就进不来了。这样就保证了这段代码的原⼦性了。

有时也把这个现象叫做同步互斥,表示操作是互相排斥的。

改为串行执行即可。

④内存可见性

可见性指, ⼀个线程对共享变量值的修改,能够及时地被其他线程看到。

Java 内存模型 (JMM-JavaMemoryModel):Java虚拟机规范中定义了Java内存模型。

目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果。

  • 线程之间的共享变量存在于"主内存" (Main Memory).
  • 每⼀个线程都有⾃⼰的 "⼯作内存" (Working Memory) .
  • 当线程要读取⼀个共享变量的时候, 会先把变量从主内存拷⻉到⼯作内存, 再从⼯作内存读取数据.
  • 当线程要修改⼀个共享变量的时候, 也会先修改⼯作内存中的副本, 再同步回主内存.

由于每个线程有自己的工作内存, 这些工作内存中的内容相当于同⼀个共享变量的 "副本". 此时修改线程1 的⼯作内存中的值, 线程2 的⼯作内存不⼀定会及时变化. 而JMM 带来的问题是会导致线程非安全问题的发生。

import java.time.LocalDateTime;

/**
 * 内存可见性问题
 */
public class ThreadDemo18 {
    //全局变量(类级别)
    private static boolean flag = true;

    public static void main(String[] args) {
        //创建子线程1
        Thread t1 = new Thread(() -> {
            System.out.println("线程 1:开始执行!" + LocalDateTime.now());
           while(flag) {
           }
            System.out.println("线程 1:结束执行!" + LocalDateTime.now());
        });
        t1.start();

        //创建子线程2
        Thread t2 = new Thread(() -> {
            //休眠1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程 2:修改 flag = false!" + LocalDateTime.now());
            flag = false;
        });
        t2.start();
    }
}

线程2早已把全局变量修改为另一个值,而线程1一直在执行,它并没有感知到全局变量flag的变化,这就是内存可见性问题。

⑤指令重排序

有很多种:

  1. 编译器指令重排序
  2. 运行期指令重排序

编译器优化是一件非常复杂的事情,其本质是调整代码的执⾏顺序,保证原有逻辑不变的情况下,提高程序执行效率。这在单线程下没问题,但在多线程并发情况下容易出现混乱,从而造成线程安全问题。

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

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

相关文章

深入理解设备像素比

文章目录参考描述像素分辨率显示分辨率图像分辨率物理分辨率分辨率单位&#xff08;仅部分&#xff09;DPIPPI设备像素比设备物理像素设备独立像素设备像素比产生放大与缩小尾声参考 项目描述关于物理像素、逻辑像素&#xff08;css像素&#xff09;、分辨率、像素比的超详细讲…

信源分类及数学模型

本专栏包含信息论与编码的核心知识&#xff0c;按知识点组织&#xff0c;可作为教学或学习的参考。markdown版本已归档至【Github仓库&#xff1a;information-theory】&#xff0c;需要的朋友们自取。或者公众号【AIShareLab】回复 信息论 也可获取。 文章目录信源分类按照信源…

Tomcat 线上调优记录

原始Tomcat配置 启动参数Plaintext-Xms256m -Xmx512m -XX:MaxPermSize128m Tomcat 参数配置XML<Executor name"tomcatThreadPool" namePrefix"catalina-exec-" maxThreads"1500" minSpareThreads"50" maxIdleTime"600000&q…

传感器原理及应用期末复习汇总(附某高校期末真题试卷)

文章目录一、选择题二、填空题三、简答题四、计算题一、选择题 1.下列哪一项是金属式应变计的主要缺点&#xff08;A&#xff09; A、非线性明显 B、灵敏度低 C、准确度低 D、响应时间慢 2.属于传感器动态特性指标的是&#xff08;D&#xff09; A、重复性 B、线性度 C、灵敏…

Centos7安装Python3

前言系统版本&#xff1a;Centos7.6python版本&#xff1a; python 3.10.4下载python下载链接&#xff1a;直通车找到对应版本的python安装包,这里以python 3.10.4为例点击3.10.4版本的链接&#xff0c;拉到最下面找到Files中对应的linux安装包鼠标右键复制下载链接登录linux系…

西电_数字信号处理二_学习笔记

文章目录【 第1章 离散随机信号 】【 第2章 维纳滤波 】【 第3章 卡尔曼滤波 】【 第4章 自适应滤波 】【 第5章 功率谱估计 】这是博主2022秋季所学数字信号处理二的思维导图&#xff08;软件是幕布&#xff09;&#xff0c;供大家参考&#xff0c;如内容上有不妥之处&#xf…

面试题记录

Set与Map的区别 map是键值对&#xff0c;set是值的集合。键&#xff0c;值可以是任何类型map可以通过get获取&#xff0c;map不能。都能通过迭代器进行for…of遍历set的值是唯一的&#xff0c;可以做数组去重&#xff0c;map&#xff0c;没有格式限制&#xff0c;可以存储数据…

Lesson4---Python语言基础(2)

4.1 内置数据结构 4.1.1 序列数据结构&#xff08;sequence&#xff09; 成员是有序排列的每个元素的位置称为下标或索引通过索引访问序列中的成员Python中的序列数据类型有字符串、列表、元组 “abc” ≠ “bac” 4.1.1.1 创建列表和元组 Python中的列表和元组&#xff0c…

阶段二10_面向对象高级_分类分包思想和案例环境搭建

一.分类思想 1.分类思想概念&#xff1a; 分工协作&#xff0c;专人干专事 2.信息管理系统分类[案例] Student 类-------------------->标准学生类&#xff0c;封装键盘录入的学生信息&#xff08;id , name , age , birthday&#xff09; StudentDao 类-----------------&…

2022年全国职业院校技能大赛(中职组)网络安全竞赛试题(3)

目录 模块A 基础设施设置与安全加固 &#xff08;本模块20分&#xff09; 一、项目和任务描述&#xff1a; 假定你是某企业的网络安全工程师&#xff0c;对于企业的服务器系统&#xff0c;根据任务要求确保各服务正常运行&#xff0c;并通过综合运用用户安全管理与密码策略、…

vTESTstudio - VT System CAPL Functions - VT7001

vtsSerialClose - 关闭VT系统通道的串行端口功能&#xff1a;关闭由系统变量命名空间指定的VT系统通道的串行端口。Target&#xff1a;目标通道变量空间名称&#xff0c;例如&#xff1a;VTS::ECUPowerSupply返回值&#xff1a;0&#xff1a;成功重置目标通道最大和最小值-1&am…

密码的世界

网络世界中常见的攻击方法 窃听攻击 窃听攻击是网络世界最常见的一种攻击方式&#xff0c;一些不能泄露的隐私信息&#xff0c;例如银行卡密码&#xff0c;账号密码&#xff0c;如果被窃听泄露的话通常会带来比较严重的后果。 中间人攻击 在中间人攻击中&#xff0c;小明准…

九龙证券|突然哑火!最火爆中小盘明显回调,后市咋走?机构最新解读

中证1000和国证2000指数创年内新高后&#xff0c;连续2日回调。 2月17日A股商场震动下行&#xff0c;创业板指数跌幅超2%&#xff0c;近3000只个股跌落&#xff0c;北向资金小幅净流入&#xff0c;商场成交额接近万亿关口&#xff0c;港股也出现显着回调痕迹。 以中小市值公司…

XSS基础——xsslabs通关挑战

目录XSS基础一、XSS基础概念1、XSS基础概念2、XSS分类二、xsslabs通关挑战level 1level 2level 3htmlspecialchars函数html事件属性level 4level 5level 6level 7level 8深入理解浏览器解析机制和XSS向量编码level 9level 10level 11level 12level 13三、总结XSS基础 一、XSS基…

算法刷刷刷| 二叉树篇| 110平衡二叉树| 257二叉树的所有路径 |404左叶子之和| 513找树左下角的值| 112路径总和| 113路径总和II

110.平衡二叉树 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 class Solution {public boolean isBalanced(TreeNode root) {int dfs…

【前端】Vue3+Vant4项目:旅游App-项目总结与预览(已开源)

文章目录项目预览首页Home日历&#xff1a;日期选择开始搜索位置选择上搜索框热门精选-房屋详情1热门精选-房屋详情2其他页面项目笔记项目代码项目数据项目预览 启动项目&#xff1a; npm run dev在浏览器中F12&#xff1a; 首页Home 热门精选滑动到底部后会自动加载新数据&a…

【factoryio】使用SCL编写 <机械手控制> 程序

使用虚拟工厂软件和博图联合仿真来编写【scl】机械手控制程序 文章目录 目录 文章目录 前言 二、程序编写 1.机械手运行部分 2.启动停止部分 3.急停复位部分 三、完整代码 总结 前言 在前面我们一起写过了许多案例控制的编写&#xff0c;在这一章我们一起来编写一下一个…

Vue中 引入使用 babel-polyfill 兼容低版本浏览器

注意&#xff1a;本文主要介绍的 vue-cli 版本&#xff1a;3.x&#xff0c; 4.x&#xff1b; 最近在项目中使用 webpack 打包后升级&#xff0c;用户反馈使用浏览器&#xff08;chrome 45&#xff09;访问白屏。经过排查发现&#xff1a;由于 chrome 45 无法兼容 ES6 语法导致的…

linux018之安装mysql

linux上安装mysql&#xff1a; 第一步&#xff1a;查看是否已经安装mariadb&#xff0c;mariadb是mysql数据库的分支&#xff0c;mariadb和mysql一起安装会有冲突&#xff0c;所以需要卸载掉。 yum list installed | grep mariadb &#xff1a;查看是否安装mariadb&#xff0c;…

「可信计算」论文初步解读

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…