JavaWeb语法三:线程不安全问题的原因和解决方案

news2024/12/23 18:54:50

目录

1.线程的状态

2.线程不安全的原因

2.1:原子性

2.2: 可见性

2.3:有序性

3.解决线程不安全问题

3.1:synchronized

3.1.1:互斥

3.1.2:可重入

3.2:volatile关键字

3.3:wait和notify

3.3.1:wait()方法

3.3.2:notify()

3.3.3notifyAll()方法

4.wait()和sleep()方法的对比(面试题)


前言:

我们如果要了解线程安全的话,首先要明白线程的状态,正如常言所说:知己知彼,方能,百战不殆。

1.线程的状态

1.new:创建了对象但未被调用start(),内核里还没有创建PCB。就好像安排了工作,还未开始行动。

2.RUNNABLE:可以工作的,又可以分成正在工作中(正在cpu上执行)和即将开始工作的。

3.TERMINATED:工作完成了,pcb执行完毕,但Thread对象还在。

我们常说阻塞状态,接下来,我们就要细说这个阻塞状态。

4.BLOCKED:排队等着其他事情的完成 ( 这是因为加锁的原因造成的。)

5.WAITING:排队等着其他事情的完成.(这是因为 wait ,join造成的)。

6.TIME_WAITING:排队等着其他事情的完成.(这是因为 sleep造成的)


2.线程不安全的原因

既然要谈到线程不安全的原因,我们首先要明白什么是线程安全,如果代码在多线程环境下运行的结果是符合我们预期的,即使在单线程环境应该的结果,则说这个程序是线程安全的。

2.1:原子性

原子性是指一个操作是不可中断的,那么全部执行成功,那么全部执行失败。即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程中断。

class count{
    public int num=0;
        public void add () {
                num++; 
        }
}
public class ThreadDemo1 {
    //原子性
    public static void main(String[] args) {
        count count=new count();
        Thread t=new Thread(()->{
            for (int i = 0; i <1000 ; i++) {
                count.add();
            }
        });
        Thread t1=new Thread(()->{
            for (int i = 0; i <1000 ; i++) {
                count.add();
            }
        });
        t.start();
        t1.start();
        try {
            t.join();
            t1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(count.num);
    }
}

看上面的代码,你会下意识认为count最后的值2000吗?这不是的哟,这是一个线程不安全的代码。我出来的结果是1515但每一次的结果都不一样。这是为啥勒?

d6abda04780648148224ad82226220f9.png

53a38083151440879beb152d1d4e3bab.png


2.2: 可见性

一个线程修改了公共变量的值,其他线程使用这个公共变量的时候,能够立即得到这个通知并修改了这个变量。

class  Counter{
    public int flag=0;
}
public class ThreadDemo2 {
    public static int num = 0;
    public static void main(String[] args) {
        Counter counter=new Counter();
        Thread t = new Thread(() -> {
           while(counter.flag==0){

           }
            System.out.println("循环结束");
        });
        Thread t2 = new Thread(() -> {
            Scanner scan=new Scanner(System.in);
            System.out.println("请输入一个数字来修改flag值");
            counter.flag=scan.nextInt();
        });
        t.start();
        t2.start();
    }
}

当t2线程修改了flag的值,首先是在自己的CPU内修改,之后再会修改内存中flag的值。之后t线程会因为编译器优化,认为flag的值不会轻易改变的,所以每次都看t线程的CPU中的flag值是否会发生变化,而不是比较内存中flag的值。编译器这种优化是为了提高速度。


2.3:有序性

有序性:代码重排序的本质就是编译器的优化。在单线程中,就是有可能发生顺序调整,但是多线程的代码执行复杂程度更高,编译器很难在编译阶段对代码执行效果进行预测。因此激进的重排序很容易导致优化后的逻辑和之前不一样。


3.解决线程不安全问题

3.1:synchronized

为了解决原子性问题,提出了synchronized(加锁)

3.1.1:互斥

synchronized会起到互斥效果,莫个线程执行到莫个对象的synchronized中时,其他线程如果也执行到同一个对象synchronized就会阻塞等待

synchronized如果修饰的普通方法,锁对象就调用者本身。

834dc5181f124297b94b2262e8787725.png

 如果修饰静态方法,锁对象就是这个类对象。

a263cd9529ac4261808335b2329c2275.png

 如果修饰的代码块,就是自己收到设置的锁对象。

f57221bc5e0e4e6380279ccc42dc1882.png

 进入synchronized修饰的代码块,相当于加锁。

退出synchronized修饰的代码块,相当于解锁。

sychrnoized是不需要手动解锁的。


3.1.2:可重入

这个意思就是说一个线程对莫个对象或者方法加锁了,但还没有解锁,又一次进行加锁,不会死锁。

可以举一个例子,就是你给一个女生表白了,之后你们在一起了,但你再表白一次,不会有啥不好是的。

6949a3a227294647a93a871b1122c0e5.png


3.2:volatile关键字

volatile能保证内存可见性。

代码在写入volatile修饰的变量的时候:

会改变线程内存中volatile变量副本的值,将改变后的副本的值从该线程的cpu刷新到内存中。

代码在读入volatile修饰的变量的时候:

从内存中读取volatile变量的最新值到该线程的cpu中,从cpu中读取volatile变量的副本。

d3da7437f1ed49528952ad1fb55492db.png

 这个代码就是解决上面可见性代码。


3.3:wait和notify

这个是解决代码的有序性。

3.3.1:wait()方法

1.使当前执行代码进行等待(把线程放到等待队列中)

2.释放当前的锁,进行阻塞等待。

3.满足一定条件时被唤醒,重新尝试获取锁

public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t=new Thread(()->{
            synchronized (lock){
                System.out.println("我这个部分已经干完了");
                System.out.println("我只等你一个小时");
                try {
                    lock.wait(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        Thread.sleep(1000);//为了让t 线程先运行
        System.out.println("别急,我正在努力赶");
    }
}

wait(参数)是毫秒,wait等待时间超时的时候,它会自动结束等待条件。

还有一种让它就是结束等待,就是有其他线程调用该对象的notify()方法。

3.3.2:notify()

随机唤醒一个处于阻塞队列的线程(是使用同一个锁对象的线程)。

在notify()方法后,当前线程不会马上释放该对象的锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t1=new Thread(()->{
            synchronized (lock) {
                System.out.println("你下班了吗?你下班了,我就来上班");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("那我来上班了");
            }
        });
        Thread t2=new Thread(()->{
            synchronized (lock){
                System.out.println("我下班了");
                lock.notify();
            }
        });
        t1.start();
        Thread.sleep(200);
        t2.start();
    }

这里为啥要t1线程启动之后,等待200毫秒,再启动线程t2了。接下来到了故事小课堂。

wait()方法就相当于一个坐校车上学的学生。notify()方法就相当于校车。如果notify()方法先启动,wait()方法后启动,就相当于校车来了,发现这个没有学生到就走了。但等到学生到的时候,就木有校车来接她。她就会一直在那里等校车来。(这就相当于这个线程一直处于阻塞队列,一直等别人来唤醒。)这样会出现线程不安全。


3.3.3notifyAll()方法

使用notifyAll()方法可以一次唤醒所有等待的线程(使用同一个锁对象的线程)。

4.wait()和sleep()方法的对比(面试题)

1.wait是object的方法,sleep是Thread的静态方法。

2.调用sleep()方法的线程不会释放对象锁,而调用wait()方法会释放对象锁。

3.使用wait()不会抛出异常,但需要搭配synchronized来使用。而sleep()会抛出异常  InterruptedException。


总结:

以上就是我总结的线程不安全的原因以及解决办法,若有错误的地方,希望各位铁子留言纠错,若感觉不错,请一键三连。

 

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

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

相关文章

傻白入门芯片设计,盘点GPU业界的大佬(十五)

在PC个人电脑时代&#xff0c;英特尔&#xff08;Inter&#xff09;是无可争议的芯片巨头&#xff0c;凭借着X86架构在数据中心CPU中的压倒性地位&#xff0c;一度垄断全球90%的市场份额。然而在人工智能时代&#xff0c;以英伟达&#xff08;NVIDIA&#xff09;为首的GPU、AI芯…

大学生心里健康

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 网站前台&#xff1a;关于我们、联系信息文章信息、咨间师信息、服务信息、测试信息 管理员功能&#xff1a; 1、管理关…

[激光原理与应用-60]:激光器 - 光学 - 光的四大理论框架与其层次:几何光学、波动光学、电磁光学、电子光学

目录 第1章 光的四大理论框架与层次 第2章 光的四大理论各自的特点 2.1 几何光学&#xff08;粒子性&#xff09;》光学特征 2.2 波动光学&#xff08;波动性&#xff09; 2.3 电磁光学&#xff08;电学性&#xff09; 2.4 量子光学&#xff08;能量&#xff09; 第1章 光…

【信管4.2】定义范围与WBS

定义范围与WBS上次课程已经说过&#xff0c;今天的内容是非常重要的&#xff0c;可以说是整个范围管理的核心内容。因此&#xff0c;也请各位打醒十二分精神&#xff0c;一起来学习这两个非常重要的过程吧。定义范围定义范围&#xff0c; 是指定项目和产品详细描述的过程&#…

Canvas库 KonvaJS入门 2坐标体系总结

Canvas库 KonvaJS入门 2坐标体系总结一、 准备环境二、konvasJS坐标基本使用演示1. 按坐标放置控件2. 移动group3. 父元素 scale 变化4. 子元素scale变化5. 旋转一、 准备环境 KonvaJS的几个属性值与坐标都有关系&#xff0c;有时候不容易分清坐标如何计算&#xff0c;本文作个…

前端基础_传统Web页面

传统Web页面 传统Web页面就是打开浏览器&#xff0c;整个页面都会打开的应用。例如&#xff0c;笔者的个人网站http://siwei.me就是一个典型的“传统Web应用”&#xff0c;每次单击其中任意一个链接&#xff0c;都会引起页面的整个刷新 传统的页面每次打开&#xff0c;都要把…

π120E60 双通道数字隔离器 完美代替ISO7820FDW

π120E60 双通道数字隔离器 完美代替ISO7820FDW 。具有出色的性能特征和可靠性&#xff0c;整体性能优于光耦和基于其他原理的数字隔离器产品。产品传输通道间彼此独立&#xff0c;可实现多种传输方向的配置&#xff0c;可实现5.0kV rms 隔离耐压等级和 DC 到 200Mbps信号传输。…

Seata实现分布式事务控制

目录 1. 启动Seata 1.1 下载seata 1.2 修改配置文件及初始化 2. 使用Seata实现事务控制 2.1 初始化数据表 2.2 添加配置 1. 启动Seata 1.1 下载seata 下载地址&#xff1a;https://github.com/seata/seata/releases/v1.3.0/ 1.2 修改配置文件及初始化 将下载得到的…

安全智能分析 思路方案

数据共享 定义内涵 数据共享 是指在多个用户或多个程序之间遵循一定规则共同享用数据&#xff0c;并进行各种操作、运算和分析的一种技术。数据共享包括数据发布、接口、交换等内容。 技术背景 随着数字经济成为拉动全球经济增长的新引擎&#xff0c;大数据成为经济中重要的…

[附源码]Node.js计算机毕业设计个人资金账户管理Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

【OpenCV-Python】教程:6-3 Epipolar Geometry 对极几何

OpenCV Python Epipolar Geometry 对极几何 【目标】 学习多视图几何学习极点、对极线、对极约束等等&#xff1b; 【理论】 当我们使用针孔相机拍摄图像时&#xff0c;我们会丢失一个重要的信息&#xff0c;即图像的深度。或者图像中的每个点距离摄像机有多远&#xff0c;…

下一个AI舞台,名叫煤矿

如果大海给贝壳下的定义是珍珠&#xff0c;那么时间给煤的定义就是钻石。2020年初&#xff0c;我们曾经探访过山西一家大型矿山。矿山中的工程师对我们说&#xff0c;现在矿上特别需要新技术&#xff0c;需要数字化、智能化。但现在年轻人&#xff0c;尤其是懂AI、懂云计算的人…

Stm32旧版库函数16——stm32 超声波测距

/******************** (C) COPYRIGHT 2012 ELC ******************** * File Name : main.c * Author : ELCWHUT * Version : V1.0 * Date : 2012-12-05 * Description : 超声波测距的STM32代码&#xff0c;采用HC-HR04…

Git全栈体系(一)

第一章 Git 概述 Git 是一个免费的、开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型到大型的各种项目。Git 易于学习&#xff0c;占地面积小&#xff0c;性能极快。 它具有廉价的本地库&#xff0c;方便的暂存区域和多个工作流分支等特性。其性能优于 Subvers…

ArcGIS创建地理处理包!让你制作的工具自由分享

喜欢就关注我们吧&#xff01; 0 前言 当用模型做好工具 分享到其他电脑 出现的模型不可用的情况 就如我们上期制作分享 的提取四至点的工具 有这个红叉的情况 因为他用到了子模型 所以发生路径不一致的情况 只要编辑配置就好了 那如何从根本上解决这个问题呢 答案是…

大数据Kudu(八):Kudu与Impala整合

文章目录 Kudu与Impala整合 一、​​​​​​​Kudu与Impala整合配置

乌班图(ubantu)部署.NET Core 6web项目(保姆教程)

1、新建文件夹&#xff0c;给读写权限 $ cd / #移动根目录&#xff08;方便好找&#xff09; 文件夹授权方式1&#xff1a;命令 $ sudo mkdir www #新建文件夹 $ cd /www $ sudo chmod 777 * -R #给读写权限 2、上传打包的项目文件&#xff0c;并解压 安装解压工具 $ sudo …

线性代数之N维向量

向量空间是线性代数的重要研究对象&#xff0c;具有广泛的应用。 1 n维向量运算 向量既有大小又有方向&#xff0c;如下表示&#xff1a; m*n个数aij(i1,2,...,m;j1,2,...,n)排成m行n列的矩形数表 若向量大小相当&#xff0c;方向相同则着两个向量相等 n个数a1,a2,...,an组成的…

SQL执行顺序

目录 1.执行顺序 2.SELECT查询时的两个顺序 3.关联过程 1.执行顺序 我们先执行from,join来确定表之间的连接关系&#xff0c;得到初步的数据 where对数据进行普通的初步的筛选 group by 分组 各组分别执行having中的普通筛选或者聚合函数筛选。 然后把再根据我们要的数据进…

00后女记者的一场直播挑战,触动了多少城市年轻打工人的心

一、00后的女记者&#xff0c;在浙江的一个小镇做了一场直播挑战&#xff0c;几天的体验并不轻松&#xff0c;却打开了一个新世界。又或者说&#xff0c;她发现了生活的另一面&#xff0c;人生的另一种可能。这个名叫濮院的小镇&#xff0c;位于浙江北部&#xff0c;桐乡辖下&a…