原来这就是线程安全(一)

@TOC

一:什么是线程不安全??

先看一段代码:

public class Demo1 {
    public static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(" count = "+count);
    }
}

在这里插入图片描述
上面的代码可能我们以为最终的count=100000;
但最终小于100000,这就是线程安全在搞鬼.
因为线程是随机调度 的,抢占式执行,就会使代码执行结果不可控,结果也就是随机的.
我们往往希望结果使我们所预期的,而这种不可控,随机的执行结果就称为bug.
多线程代码,引起了bug,这样的问题就是"线程安全问题",存在线程安全问题的代码,就称为"线程不安全".

二:为什么代码执行结果不是100000

count++操作其实是三个指令:
(1)把内存中的count中的数值,读到CPU寄存器中.(load)
(2)把寄存器中的值+1,(add).
(3)把寄存器上述计算后的值,写回到内存的count里.(save)
但因为不止一个线程,某个线程在执行指令的过程中,当它执行到任何一个指令的时候,由于随机调度,抢占式执行,就有可能被其他线程把它的CPU给抢占走.(操作系统把前一个线程调度走,后一个线程执行).
下面列举两个线程并发执行的时候,可能的执行指令的顺序,(不同的执行顺序,得到的结果可能存在差异)
在这里插入图片描述

在这里插入图片描述

我们知道count数据是存在内存中的,而同一个进程,不同的线程是共用一块内存资源的,也就是t1,t2线程操作的是同一块内存地址.
以下图为例:
在这里插入图片描述
1: t1线程 load操作拿到count的值,拿到了0,放到了A寄存器中;
2:t2线程 load 操作拿到count的值,拿到了0,放到了B寄存器中;
3:t1线程执行add操作,将A寄存器中的值+1,
4:t2线程执行add操作,将B寄存器中的值+1,
5:t1线程执行save 操作,将A寄存器中的值写回到内存中,内存中的值由0变为1.
6:t2线程执行save 操作,将B寄存器中的值写回到内存中,内存中的值由1变为1.

三:出现线程不安全的原因:

1:线程在系统中是随机调度,抢占式执行的,
2:当前代码中,多个线程同时修改同一个变量.

如果只有一个线程,那么修改操作没问题.
如果多个线程是读取操作,那么也没事.
如果多个线程修改不同的变量,也没事.
3:线程针对变量的修改操作不是"原子"的,而是三条指令.
但有的修改操作,是原子的,比如直接对变量进行赋值操作(CPU中就一个move指令).
4:内存可见性问题,引起的线程不安全
5:指令重排序,引起的线程不安全

四:解决线程不安全问题

线程不安全原因1:我们不能修改,因为硬件方面就是这样设计的(内核就是这样设计的 ).
原因2:不能修改,因为此时场景就是多线修改同一个变量.
原因3:可以通过"加锁"操作,将多个指令打包成一个整体.
通过"加锁",就达到了"互斥"的效果
互斥:t1线程在执行的时候,t2线程不能执行(阻塞等待),t2线程在执行的时候,t1线程不能执行(阻塞等待),互斥也称为锁竞争,锁冲突.

1:锁的操作:

(1)加锁:t1加上锁之后,t2也尝试加锁,就会阻塞等待(都是系统内核控制)(在Java中可以看到BLOCKED状态)
(2)解锁:直到t1解锁了之后,t2才有可能拿到锁(加锁成功).

2:编写代码

首先创建一个对象,使用这个对象作为锁:
在Java中可以使用任何对象作为加锁对象.
创建锁对象的意义:
锁对象的用途:有且只有一个,那就是用来区分,多个线程是否针对同一个对象(count)加锁,如果是,就会出现锁竞争/锁冲突/互斥,就会引起阻塞等待;如果不是,就不会出现锁竞争,也就不会阻塞等待.
通过设定不同的锁对象,来确定竞争关系.

public class Demo2 {
    public static int count=0;

    public static void main(String[] args) throws InterruptedException {
        //首先创建一个对象,使用这个对象作为锁
        Object locker = new Object();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
              synchronized (locker){
                  count++;
              }

            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                synchronized (locker){
                    count++;
                }

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " +count);
    }
}

2.1 synchronized (){}

()里面写"锁对象"
{}:当进入代码块,就相当于对锁对象进行了加锁操作.
当出了代码块,就相当于对锁对象进行了解锁操作.

public class Demo2 {
    public static int count=0;

    public static void main(String[] args) throws InterruptedException {
        //首先创建一个对象,使用这个对象作为锁
        Object locker = new Object();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
              synchronized (locker){
                  count++;
              }

            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                synchronized (locker){
                    count++;
                }

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " +count);
    }
}

上述代码就相当于两个线程,针对同一个锁对象加锁,就会产生互斥.
t1,t2做的事情就是:判断循环条件,加锁,load ,add, save ,解锁,i++;
假设:由于是线程是并发执行的,这里假设t1线程先执行到了加锁操作,并且t1还没解锁,那么t2线程就不能获得锁对象,就不能进行加锁操作,
假设t1刚解锁,t2 就加锁了,但t1线程并不是什么代码也不执行,而是继续执行i++,循环判断条件,当t1执行到了synchronized,发现不能获得锁对象,那么t1线程只好阻塞等待了.
因此:在t1,t2两个线程中,每次count++是存在锁竞争的,会变成"串行"执行,但是执行for 循环中的条件以及i++仍然是并发执行的.

public class Demo2 {
    public static int count=0;

    public static void main(String[] args) throws InterruptedException {
        //首先创建一个对象,使用这个对象作为锁
        Object locker = new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
              synchronized (locker){
                  count++;
              }

            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                synchronized (locker2){
                    count++;
                }

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " +count);
    }
}

上述代码就是两个线程针对不同对象加锁,就不会产生互斥,也就不会发生阻塞等待现象了.
操作系统中的加锁,解锁功能,核心还是CPU提供的指令(硬件提供了这样的能力,软件上才有对应的功能)

3:加锁的变种写法:

3.1:创建一个自定义类,并把这个类的引用作为加锁对象

class Count {
    public int count;
    public void add(){
        count++;
    }
    public int get(){
        return count;
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Count count=new Count();
        Thread t1=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                synchronized (count){
                    count.add();
                }

            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                synchronized (count){
                    count.add();
                }

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count= " +count.get());

    }
}

3.2: synchronized (this){}

class Count1 {
    public int count;
    public void add(){
        synchronized (this){
            count++;
        }

    }
    public int get(){
        return count;
    }

}
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Count1 count1=new Count1();
        Thread t1=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                count1.add();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                count1.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = "+count1.get());
    }
}

3.3: synchronized public void add(){}

class Count1 {
    public int count;
    synchronized public void add(){
            count++;
    }
    public int get(){
        return count;
    }

}
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Count1 count1=new Count1();
        Thread t1=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                count1.add();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                count1.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = "+count1.get());
    }
}

3.4:针对类对象加锁

class Count1 {
    public static int count;
   static void func(){
       synchronized (Count1.class){
           count++;
       }
   }
   public int get(){
       return count;
   }


public class Demo2 {

    }
    public static void main(String[] args) throws InterruptedException {
        Count1 count1=new Count1();
        Count1 count2=new Count1();
        Thread t1=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {

            count1.func();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
           count2.func();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = "+count1.get());
    }
}

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

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

相关文章

Linux-进程控制

&#x1f30e;进程控制【上】 文章目录&#xff1a; 进程控制 为什么要有地址空间和页表 程序的内存       程序申请内存使用问题 写时拷贝与缺页中断 父子进程代码共享       为什么需要写时拷贝       页表的权限位       缺页中断 退出码和错误码…

P3369 【模板】普通平衡树(splay 算法)

题目描述 您需要写一种数据结构&#xff08;可参考题目标题&#xff09;&#xff0c;来维护一些数&#xff0c;其中需要提供以下操作&#xff1a; 插入一个数 x。删除一个数 x&#xff08;若有多个相同的数&#xff0c;应只删除一个&#xff09;。定义排名为比当前数小的数的…

Pytorch从零开始实战22

Pytorch从零开始实战——CycleGAN实战 本系列来源于365天深度学习训练营 原作者K同学 内容介绍 CycleGAN是一种无监督图像到图像转换模型&#xff0c;它的一个重要应用领域是域迁移&#xff0c;比如可以把一张普通的风景照变化成梵高化作&#xff0c;或者将游戏画面变化成真…

2024软件设计师备考讲义——UML(统一建模语言)

UML的概念 用例图的概念 包含 <<include>>扩展<<exted>>泛化 用例图&#xff08;也可称用例建模&#xff09;描述的是外部执行者&#xff08;Actor&#xff09;所理解的系统功能。用例图用于需求分析阶段&#xff0c;它的建立是系统开发者和用户反复…

4G/5G防爆布控球

#防爆布控球 #远程实时监控 #移动应急指挥 #高清图像采集 #防爆安全认证 4G/5G防爆布控球 M130-EX防爆布控球是针对石化装置、石油平台、燃气、化工、制药、煤炭、冶炼、船舶制造、纺织等易燃易爆环境及危险场所而开发设计的防爆智能一体化电气设备。 产品型号&#xff1a;M13…

Antd Vue3 使用 Anchor 锚点组件记录

项目场景 客户要求做一个表单页面&#xff0c;表单数据分为三步&#xff0c;每一步骤是一个单独的 Vue 组件&#xff0c;表单上方需要使用锚点组件实现锚点定位到每一步的功能。 代码总览 <template><div class"guided-form-content-wrapper"><!-- …

CKS之Kubernetes审计日志

目录 概述 审计事件阶段 审计日志级别 None Metadata Request RequestResponse 审计日志的使用 步骤1&#xff1a;配置审计策略文件 步骤2&#xff1a;配置API Server 步骤3&#xff1a;配置日志存储 注意事项 审计策略与规则 审计日志样例 使用场景 概述 Kube…

一、JAVA集成海康SDK

JAVA集成海康SDK 文章目录 JAVA集成海康SDK前言一、项目依赖 jar1. examples.jar2. 项目依赖 jna.jar,可以通过 maven依赖到。二、集成SDK1.HcNetSdkUtil 海康 SDK封装类2.HCNetSDK3.Linux系统集成SDK三、总结前言 提示:首先去海康官网下载 https://open.hikvision.com/dow…

stable diffusion如何下载模型?

文件夹里面有14个模型&#xff0c;把这些模型复制到SD文件夹里 具体位置:SD文件>models>ControlNet

【C/C++】从零开始认识C++历程-启航篇

文章目录 &#x1f4dd;前言&#x1f320; 什么是C&#xff1f;&#x1f309;C的发展史 &#x1f320;C的重要性&#x1f309;语言的使用广泛度 &#x1f320;在工作领域&#x1f309; 岗位需求 &#x1f320;相关笔试题&#x1f309; 公司怎样面试C &#x1f6a9;总结 &#x…

蓝桥杯 - 小明的背包1(01背包)

解题思路&#xff1a; 本题属于01背包问题&#xff0c;使用动态规划 dp[ j ]表示容量为 j 的背包的最大价值 注意&#xff1a; 需要时刻提醒自己dp[ j ]代表的含义&#xff0c;不然容易晕头转向 注意越界问题&#xff0c;且 j 需要倒序遍历 如果正序遍历 dp[1] dp[1 - vo…

java的多态和final关键字

多态&#xff1a; 多态分为对象多态&#xff0c;行为多态 多态的前提&#xff1a; 有继承/实现关系&#xff1b;存在父类引用子类对象&#xff1b;存在方法重写&#xff1b; 注意&#xff1a;多态是对象&#xff0c;行为的多态&#xff0c;java的成员变量不谈多态 这是我写…

将Knife4j所展示请求参数和响应参数转化为TS类型声明

目标&#xff1a;在浏览器控制台输入js代码&#xff0c;将读取页面所展示的请求参数和响应参数&#xff0c;将他们转化为TS的类型声明&#xff0c;并在控制台中输出出来。 将Knife4j所展示请求参数和响应参数转化为TS类型声明 1 找到所需要的元素节点2 转化元素节点3 封装成函…

本地部署的stable diffusion 如何更新controlnet?

stable diffusion 未启动状态 点击“版本管理” 点击“扩展” 找到controlnet&#xff0c;点击右边的“更新”按钮 完成&#xff01;

【软考---系统架构设计师】特殊的操作系统介绍

目录 一、嵌入式系统&#xff08;EOS&#xff09; &#xff08;1&#xff09;嵌入式系统的特点 &#xff08;2&#xff09;硬件抽象层 &#xff08;3&#xff09;嵌入式系统的开发设计 二、实时操作系统&#xff08;RTOS&#xff09; &#xff08;1&#xff09;实时性能…

总结TCP各类知识点

前言 本篇博客博主将详细地介绍TCP有关知识点&#xff0c;坐好板凳发车啦~ 一.TCP特点 1.有连接 TCP传输的过程中类似于打电话的各个过程 2.可靠传输 通过TCP自身的多种机制来保证可靠传输 3.面向字节流 内容是以字节的方式来进行发送与接收 4.缓冲区 TCP有接收缓冲区…

网络安全接入认证-802.1X接入说明

介绍 802.1X是一个网络访问控制协议&#xff0c;它可以通过认证和授权来控制网络访问。它的基本原理是在网络交换机和认证服务器之间建立一个安全的通道&#xff0c;并要求客户端提供身份验证凭据。如果客户端提供的凭据是有效的&#xff0c;交换机将开启端口并允许访问。否则&…

服务器被挖矿了怎么办,实战清退

当我们发现服务器资源大量被占用的时候&#xff0c;疑似中招了怎么办 第一时间重启服务是不行的&#xff0c;这些挖矿木马一定是会伴随着你的重启而自动重启&#xff0c;一定时间内重新霸占你的服务器资源 第一步检查高占用进程 top -c ps -ef 要注意这里%CPU&#xff0c;如果…

1.8.1 摄像机

一、摄像机 OpenGL本身没有摄像机的概念&#xff0c;但是我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机&#xff0c;产生一种我们在移动的感觉&#xff0c;而不是场景在移动。 本节将会讨论如何在OpenGL中配置一个摄像机&#xff0c;让你能够在3D场景中…

Web APIs

文章目录 Web APIs1. DOM1.1 介绍DOM 树DOM 节点document 1.2 获取DOM对象1.3 操作元素内容1.4 操作元素属性常用属性修改控制样式属性操作表单元素属性自定义属性 1.5 间歇函数1.6 事件事件监听事件类型事件处理程序 1.7 事件类型鼠标事件键盘事件焦点事件文本框输入事件 1.8 …