Java线程 - 详解(2)

news2024/12/22 19:57:55

一,线程安全问题

有些代码在单个线程的环境下运行,完全正确,但是同样的代码,让多个线程去执行,此时就可能出现BUG,这就是所谓的 "线程安全问题"。举一个例子:

public class Demo {
    static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        });
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        });
        thread.start();
        thread1.start();

        thread.join();
        thread1.join();
        System.out.println("count = "+count);
    }
}

 以上代码如果使用单线程执行,他的答案是10000,但是如果使用多线程,那么就不确定了。

 那么为什么会出现这种情况呢?首先我们要深入了解一下 count++ 这个操作,实际上这个操作是分成 3 步执行的(站在CPU的角度,count++,是有三个指令实现的):

  1. load 把数据从内存读取到 CPU 寄存器中   ——>  tmp = count (简单理解版)
  2. add 把寄存器中的数据进行 +1                   ——>  tmp += 1      (简单理解版)
  3. save 把寄存器中的数据,保存到内存中    ——>  count = tmp  (简单理解版)

在此基础上,又因为线程之间的调度顺序是随机的,就会导致上面的代码出BUG,画一个图来理解一下:

 (上面画的只是一部分情况),也就是说,两个线程分别自增一次,预期得到的是2,实际上可能得到的是1,这就会导致两个线程的结果没有向上累加,而是各自独立运行。

讲了上面的BUG后,还有一个问题,我们得到的count的可能取值范围是多少?是[1,10000],还是[5000,10000]?答案是 [1,10000],因为可能 线程1 自增1次, 而 线程2 自增 n 次,画个图理解一下:

 二,线程安全问题产生原因

1. 操作系统中,线程的调度顺序是随机的(抢占式执行)

        线程的调度顺序是在系统内核实现的,无法解决

2. 两个线程,针对同一个变量进行修改​​​​​​​

        一个线程针对一个变量进行修改 ✔️

        两个线程针对不同变量进行修改 ✔️

        两个线程针对同一个变量进行读取 ✔️

3. 修改操作不是原子的,拿上面的举例就是:count++这个操作不是一步到位的,需要分成4三步执行。

        原子性:将多个操作"封装"起来,使其就相当于一个操作,更加通俗一点,就相当于

        有一个房间,当线程1在该房间执行操作时,线程2要么等待,要么去其他房间执行

4. 内存可见性问题(这章还不涉及)

5. 指令重排序问题(这章还不涉及)

 三,解决线程安全问题

上面讲了5种原因导致线程安全问题,其中1是避免不了的,2是只能在写代码时尽量避免,4,5还没涉及到,因此我们要想解决线程安全问题就只能从原因3入手,即将那些非原子操作转换成原子操作,更加专业一点就是 "加锁"。

在Java中,给代码"加锁"最常见的办法就是使用 synchronized 关键字:

synchronized( ){

        ......

        //要执行的操作放在这里

}

注:( )中需要一个用来加锁的对象。这个对象的类型不重要,重要的是通过这个对象来区分两个线程是否竞争同一个锁,如果两个线程是在竞争同一个锁,就会有锁竞争,如果不是,就不会有锁竞争,就任然是并发执行。

你可以将锁想象成一个单人的自习室,如果你先占用了这个自习室,那么其他人要么阻塞等待你使用结束(锁竞争),要么去其他空的自习室(没有锁竞争)。

public class Demo {
    static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();//锁
        Thread thread = new Thread(()->{
            synchronized (locker){
                for (int i = 0; i < 5000; i++) {
                    count++;
                }
            }
        });
        Thread thread1 = new Thread(()->{
            synchronized (locker){
                for (int i = 0; i < 5000; i++) {
                    count++;
                }
            }
        });
        thread.start();
        thread1.start();

        thread.join();
        thread1.join();
        System.out.println("count = "+count);
    }
}

 在画个图对比一下:

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

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

相关文章

Ansible学习笔记12

playbook&#xff1a; playbook&#xff08;剧本&#xff09;&#xff1a;是ansible用于配置、部署和管理被控节点的剧本&#xff0c;用于Ansible操作的编排。 使用的是yaml格式&#xff0c;&#xff08;saltstack、elk、docker、docker-compose、k8s都会使用到yaml格式。&am…

c语言实现二叉树(链式结构)

文章目录 前言一、二叉树的遍历1、二叉树的层序遍历2、二叉树的前序遍历3、二叉树的中序遍历4、二叉树的后序遍历5、代码实现 二、二叉树的一些操作的实现1、求二叉树的结点个数2、求二叉树叶子结点个数3、求二叉树第k层结点个数4、求二叉树深度5、二叉树中查找值为x的结点6、二…

彻底理解浏览器的缓存机制

前言 在前端性能优化的方式中&#xff0c;最重要的当然是缓存了&#xff0c;使用好了缓存&#xff0c;对项目有很大的帮助。比如我们访问网页时&#xff0c;使用网页后退功能&#xff0c;会发现加载的非常快&#xff0c;体验感很好&#xff0c;这就是缓存的力量。 什么是缓存…

SpringBoot的四种handler类型

Controller ReuestMapping 实现Controller接口 使用Component将该类封装成一个Bean 实现HttpRequestHandler 实现RouterFunction

upload-labs文件上传漏洞靶场练习

任意文件上传靶场upload-labs下载地址 文章目录 Pass-01- 前端JS校验绕过Pass-02- 文件类型MIME类型绕过Pass-03- 文件名后缀黑名单绕过Pass-04- .htaccess绕过Pass-05- 文件名后缀大写绕过Pass-06- 文件名后缀加空格绕过Pass-07- 文件名后缀加点绕过Pass-08-文件名后缀 ::$DAT…

控制goroutine 的并发执行数量

goroutine的数量上限是1048575吗&#xff1f; 正常项目&#xff0c;协程数量超过十万就需要引起重视。如果有上百万goroutine&#xff0c;一般是有问题的。 但并不是说协程数量的上限是100多w 1048575的来自类似如下的demo代码: package mainimport ( "fmt" "ma…

SpringMVC使用

文章目录 一.MVC基础概念1.MVC定义2.SpringMVC和MVC的关系 二.SpringMVC的使用1.RequestMapping2.获取参数1.获取单个参数2.传递对象3.后端参数重命名&#xff08;后端参数映射&#xff09;4.获取URL中参数PathVariable5.上传文件RequestPart6.获取Cookie/Session/header 3.返回…

电视盒子什么牌子好?经销商整理线下热销电视盒子品牌排行榜

在面对众多品牌和型号时&#xff0c;不知道电视盒子哪个牌子好的消费者超多&#xff0c;很多人进店都会问我电视盒子哪款好&#xff1f;我根据店铺内近两年的销量情况整理了电视盒子品牌排行榜&#xff0c;看看实体店哪些电视盒子最值得入手吧。 TOP 1.泰捷WEBOX 40Pro Max电视…

案例实操-获取员工数据

案例&#xff1a;获取员工数据&#xff0c;返回统一响应结果&#xff0c;在页面渲染展示 package com.bignyi.controller;import com.bignyi.pojo.Emp; import com.bignyi.pojo.Result; import com.bignyi.utils.XmlParserUtils; import org.springframework.web.bind.annotat…

分享一个在线二维码生成器(基于qrcode.js开发)

一种二维码扫描与生成的工具, 它可生成个性化二维码, 支持文本、网址、图片、短信、电话等格式及主题,提供融合码功能 演示地址 https://qrcode.gitapp.cn 关键代码 var qrcode new QRCode(document.getElementById("qrcode"), {text: "",width: 288,h…

2023下半年西安/北京/深圳NPDP产品经理国际认证开班啦

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…

【JavaSE】Java快速入门

Java main 函数 public class Main {public static void main(String[] args) { System.out.printf("Hello and welcome!");} }与C命名规范不同&#xff0c;Java 的命名形式最好使用驼峰法 Java 注释 C/C常用的两种注释习惯Java都可以使用&#xff0c;Java自身…

【HASH值获取】

命令行输入&#xff1a;C:\Users\Administrator>certutil -hashfile SIC-1000.exe md5

2、[春秋云镜]CVE-2022-30887

文章目录 一、靶标介绍二、复现过程 一、靶标介绍 二、复现过程 &#xff08;1&#xff09;打开网址。 &#xff08;2&#xff09;查看源代码 邮件格式&#xff1a;第一个符号不准为&#xff0c;后续符号有、.&#xff1b; 密码格式&#xff1a;匹配所有小写字母&#xff0c…

数组 刷题常用

在写数组模拟常用到数组&#xff0c;借此把常用的记下来以便查阅 一维数组&#xff0c;若初始化为0&#xff0c;可以用int a[N] {0}或者int a[N]{}. 但是若是其他值&#xff0c;不可类似地初始化为int a[N] {0}&#xff0c;而应写成memset或者fill赋值的方法。 首先便是二维…

康希诺的再估值:市场到底,行业向上

生物医药是整个二级市场弹性数一数二&#xff0c;但拐点难以揣摩的行业。这一点&#xff0c;美港A三大市场都曾经有过足够多的暴涨暴跌案例可用于佐证。 但很多时候&#xff0c;这种片面的表现又掩盖了生物医药自身的永续价值&#xff1a;在绝大多数细分赛道上&#xff0c;任何…

激活Conda环境并在pycharm使用

第一步&#xff1a;打开Anaconda Prompt 第二步&#xff1a;查看当前存在的虚拟环境 conda env list 第三步&#xff1a;创建虚拟环境 conda create -n 环境名 pythonX.X.X 如果不清楚python版本&#xff0c;可以用以下命令查看&#xff1a; 第四步&#xff1a;激活指定虚拟环…

客户案例|MemFire Cloud助推应急管理业务,打造百万级数据可视化大屏

「导语」 硬石科技&#xff0c;成立于2018年&#xff0c;总部位于武汉&#xff0c;是一家专注于应急管理行业和物联感知预警算法模型的技术核心的物联网产品和解决方案提供商。硬石科技作为一家高新技术企业&#xff0c;持有6项发明专利&#xff0c;拥有100余项各类平台认证和资…

nginx+tomcat部署的项目,上传文件成功,但请求文件报403 forbidden

这是因为上传文件时tomcat创建的目录、文件&#xff0c;nginx没有权限访问导致。 最快的解决方法是修改$tomcat_home/bin/catalina.sh 修改之后记得重启tocmat 参考&#xff1a; https://www.cnblogs.com/mgds/p/16129039.html