[JavaEE初阶] 线程安全问题的原因和解决方案

news2025/1/16 18:40:48

努力努力,月薪过亿!!!
格局打开~~~

文章目录

  • 前言
  • 1. 线程安全问题的概念
  • 2. 线程安全问题的原因
  • 3. 线程安全问题解决--加锁
  • 3. synchronized
  • 4. 死锁
    • 4.1 产生死锁的情况
    • 4.3 产生死锁的必要条件
    • 4.4 避免死锁的方法


前言

线程安全这里可能会出道面试题,在日常工作中也是很重要的内容.下面,我们来具体探讨一下吧~~


1. 线程安全问题的概念

首先,什么是不安全的线程呢?
线程是抢占式执行,随机调度,所以,线程调度的顺序不可预知.所以,必须在所有可能的调度顺序下,都能保证正确的结果,这样的线程为安全线程,否则为不安全的线程.

如下代码

class Counter{
    public int count = 0;
    public void add(){
        count++;
    }
}
public class ThreadAdd {
    public static void main(String[] args) {
        Counter c = new Counter();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10_0000; i++) {
                c.add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10_0000; i++) {
                c.add();
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count的值为" + c.count);
    }
}

执行结果如下,两个线程分别对count执行+100000操作,结果却比200000小,这是为什么呢?
在这里插入图片描述
由于i++操作需要三个操作才能执行完.
1.先把内存中的值,读取到CPU寄存器中,load.
2.进行++操作,add.
3.把结果写回到内存中.save.
由于两个线程进行++操作,线程调度顺序有多少种呢?大家猜一猜
答案:有无数种.
为啥呢?
我们看下图,以此类推,有无数种排列方式.那他们的结果如何呢?
在这里插入图片描述

上图第一种,t1从内存中拿到count值0,执行+1操作,count = 1,再放回内存,t2从内存中拿到count值1,执行+1操作,count = 2,再放回内存,这个顺序是没问题的.

上图第二种,t1先拿到count值0,t2也取出count值0,t2执行+1操作,count = 1,t1执行+1操作,count = 1,t2把count值1放回寄存中,之后t1再把count值1覆盖到寄存器中,此时,两个线程执行完++操作,最终count值为1.
之后的操作类似.
我们发现,只有像上图第一种第三种这样把操作中的load,add,save集中操作的才是结果正确的.其余全都不对.

2. 线程安全问题的原因

1.产生安全问题的根本原因是线程是抢占式执行,随即调度
2.与代码结构有关,出现多个代码同时修改同一个变量的情况,导致最终结果不可预控.上图就是这种情况.
3.操作不是原子性,就是类似上图的++操作,load,add,save如果必须是全都一次性执行完才能执行下次的++操作,就能避免线程安全的问题.
4.内存可见性问题,如果我们在读数据的时候,这个数据正在被另一个修改,那么,这个读到的数据就不是正确的.
5.指令重排序,这是编译器优化产生的bug,有时,编译器觉得你的代码复杂度太高了,自作主张给你的代码优化了,产生了结果的不可预知.
以上五个问题不是全部原因,具体问题具体分析,不可一概而论.

3. 线程安全问题解决–加锁

我们从原子性方面解决问题,我们将++操作原子化,完整执行完一次++操作后,才能执行下次的++操作.

	synchronized public void add(){
        count++;
    }

在这里插入图片描述

用synchronized修饰add()方法,对调用add的对象c加锁,只有执行完add()方法之后,出去了synchronized修饰的范围,程序会自动给对象c解锁.
c加锁过程中,别的对象若想调用对象c,就会造成线程阻塞,必须等待c执行完add()函数,才有机会使用对象c.实现了c调用add()函数时,进行++操作的原子性.

3. synchronized

1.synchronized修饰普通方法
调用方法时,对调用的对象加锁,进方法自动加锁,出方法自动解锁.

	synchronized public void add(){
        count++;
    }

2.synchronized修饰静态方法
调用方法时,对这个类进行加锁,线程调用这个静态方法时,别的线程无法使用该类

	synchronized public static void fun(){
        System.out.println("这里是synchronized修饰静态方法");
    }

3.synchronized修饰代码块
如下面代码,缩小了锁的范围,进代码块对调用方法的对象加锁,出代码块,自动对对象解锁.但要注意的是,这里需要手动指定加锁的对象,可根据需要自行指定.

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

4. 死锁

死锁是一个很重要很麻烦的事情,一旦出现死锁,线程就无法继续执行.但死锁很隐蔽,开发时不经意间会写出来,测试时,又不容易测出来,比较麻烦~

4.1 产生死锁的情况

1.一个线程已经对一个对象加锁了,又尝试对这个对象再加一把锁.如下代码所示.这时如果锁是可重入锁,线程正常执行,否则,就会导致死锁.很幸运,Java中的synchronized是可重入锁,但C++,Python中的锁是不可重入锁,同一个线程对一个对象加两把锁,就会导致死锁.

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

2.两个线程互相等待对方的锁,造成循环等待,产生死锁.
如下代码所示,线程t1对对象d1加锁,同时线程t2对对象d2加锁,之后,t1再尝试对d2加锁,同时t2再尝试对对象d1加锁.两个线程都在等待对方释放资源,互不相让,造成循环等待,产生死锁.

public class ThreadLockProblem {
    public static void main(String[] args) {
        Object d1 = new Object();
        Object d2 = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (d1) {

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (d2) {
                    System.out.println("汤老湿把酱油和醋都拿到了");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (d2) {

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (d1) {
                    System.out.println("师娘把酱油和醋都拿到了");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

结果如下,产生死锁,不打印任何内容.
在这里插入图片描述
3.多个线程多把锁,哲学家问题,如下图,每个哲学家左手右手都有一根筷子,只有拿到两只筷子的人才能吃到好吃的.如果所有人都同时拿起左手边的筷子,那么所有人都要等另一只筷子,导致了死锁.在这里插入图片描述

4.3 产生死锁的必要条件

1.互斥使用,这是线程的基本特性.线程1拿到资源后,其他线程也想使用就只能等待.
2.不可抢占,线程1对对象A加锁后,其他线程若想对对象A加锁,便只能等待线程1释放锁才行
3.请求和保持,线程1已经对对象A加锁时,再尝试对对象B加锁,此时,线程仍可以保持对对象A的锁.
4…循环等待,线程t1对对象d1加锁,同时线程t2对对象d2加锁,之后,t1再尝试对d2加锁,同时t2再尝试对对象d1加锁.两个线程都在等待对方释放资源,互不相让,造成循环等待.

以上前三种是线程的特性,不可强制改变,唯一可控的是第四点.
避免出现循环等待.

4.4 避免死锁的方法

给对象加锁时,给对象锁编号,线程都按照固定的顺序进行加锁.
线程1先对对象A加锁,再对B加锁,线程2也先对对象A加锁,再对对象B加锁.如下图所示
在这里插入图片描述


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

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

相关文章

MathType公式对齐不正确

MathType公式对齐不正确1.软件环境⚙️2.问题描述&#x1f50d;3.解决方法&#x1f421;4.1.通过标尺对齐4.2.通过输入具体的制表符位置对齐1.软件环境⚙️ Windows10 教育版64位 Word 2021 MathType 7 2.问题描述&#x1f50d; 在使用Word写论文的时候&#xff0c;总是避免不…

JavaScript 模块:理解模块系统

前言 现代JavaScript开发毋庸置疑会遇到代码量大和广泛使用第三方库的问题。解决这个问题的方案通常需要把代码拆分成很多部分&#xff0c;然后再通过某种方式将它们连接起来。 在ECMAScript 6模块规范出现之前&#xff0c;虽然浏览器原生不支持模块的行为&#xff0c; 但也迫…

ssh连接ubuntu报错

记录问题&#xff1a;1我在本机windows用ssh rootubuntu连接失败 显示端口21啥的2 打开Ubuntu系统&#xff0c;输入ps -e|grep ssh&#xff0c;发现只有agent&#xff0c;没有server3 安装ssh server&#xff0c;输入sudo apt-get install openssh-server&#xff0c;发现报错信…

仅需一个注解,实现 SpringBoot 项目中的隐私数据脱敏!

这两天在整改等保测出的问题&#xff0c;里面有一个“用户信息泄露”的风险项&#xff08;就是后台系统里用户的一些隐私数据直接明文显示了&#xff09;&#xff0c;其实指的就是要做数据脱敏。数据脱敏&#xff1a;把系统里的一些敏感数据进行加密处理后再返回&#xff0c;达…

一键自动化 | Salesforce发布Automation Anywhere自动化组合!

2022年12月1日&#xff0c;Salesforce推出了一个新的Automation Everywhere Bundle&#xff0c;以加速端到端的工作流编排&#xff08;Workflow Orchestration&#xff09;、跨系统自动化&#xff0c;以及在任何地方嵌入数据和AI驱动的工作流。 该捆绑包完全集成到Salesforce F…

acwing第84场周赛(4788,4789,4890)题解

4788. 最大数量 某商场在一天中一共来了 nn 个客人。 每个客人进入商场的具体时刻&#xff08;精确到分钟&#xff09;已知。 请你计算并输出在同一时刻&#xff08;精确到分钟&#xff09;进入商场的最大客人数量。 输入格式 第一行包含整数 nn。 接下来 nn 行&#xff…

二叉搜索树比起二叉树又有什么不一样呢?

二叉搜索树比起二叉树又有什么不一样呢&#xff1f;&#x1f3d0;什么是二叉搜索树&#x1f3d0;二叉搜索树的实现&#x1f3c0;节点类:&#x1f3c0;构造函数&#x1f3c0;析构函数&#x1f3c0;插入insert⚽非递归版本⚽递归版本&#x1f3c0;查找find⚽非递归版本⚽递归版本…

spring boot 八:SpringBoot响应返回xml数据

spring boot 八&#xff1a;SpringBoot响应返回xml数据 1 前言 根据DispatcherServlet源码分析&#xff0c;研究SpringBoot的Controller返回xml数据的一些方法&#xff0c;包含单独配置和全局配置返回xml数据两种方式。 依赖的SpringBoot版本&#xff1a; <parent>&l…

u盘有病毒怎么办?修复U盘,3个方法解决

U盘和外部的驱动器相比&#xff0c;它的体积更小&#xff0c;携带更加方便&#xff0c;可以轻松地与他人分享文件。虽然U盘使用很方便&#xff0c;但是有时会出现中病毒的情况。u盘有病毒怎么办&#xff1f;如果您也受到此问题的影响&#xff0c;我们可以提供一种有效的方法来修…

物联网架构实例—Ubuntu 安装Redis

1.准备更新apt-get源sudo apt-get update2.安装执行Redis 安装命令sudo apt-get install redis-server3.检查安装状态sudo /etc/init.d/redis-server status查看Redis运行进程ps -aux|grep redis4.将Redis添加到服务器启动项修改/etc/rc.localvim /etc/rc.local将下面的命令加到…

阿里云办公安全产品专家高传贵:零信任,让全球办公安全更简单

2022 年 8 月 30 日&#xff0c;阿里云用户组&#xff08;AUG&#xff09;第 9 期活动在北京举办。活动现场&#xff0c;阿里云办公安全产品专家高传贵&#xff0c;向参会企业代表分享了零信任&#xff0c;让全球办公安全更简单。本文根据演讲内容整理而成。 大家下午好。我今天…

内部类导致的内存泄漏

前两天刷文章偶然翻到一篇因使用非静态内部类时导致内存泄漏的问题,出于好奇自己也动手一试 什么叫内存泄漏 内存泄漏&#xff08;Memory Leak&#xff09;是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放&#xff0c;造成系统内存的浪费&#xff0c;导致程序…

WuThreat首个发布全球领先的身份安全云产品ITDR Cloud

随着数字化、人工智能&#xff0c;公有/私有云&#xff0c;物联网络及5G等技术的全面普及和迭代更新&#xff0c;身份管理建设作为企业重要的基础设施。然而现在黑客攻击手段复杂多样&#xff0c;在历年的实战攻防演习中有大量的应用系统与基础设施的的身份入口被攻破&#xff…

【从零开始学习深度学习】38. Pytorch实战案例:梯度下降、随机梯度下降、小批量随机梯度下降3种优化算法对比【含数据集与源码】

本文将使用一个来自NASA测试不同飞机机翼噪音的数据集&#xff0c;通过梯度下降、随机梯度下降、小批量随机梯度下降这3种优化算法进行模型训练&#xff0c;比较3种训练结果的差异。 目录1. 梯度下降、随机梯度下降、小批量随机梯度下降区别2. 读取训练数据3. 从零实现3种梯度算…

多线程与高并发(16)——线程池原理(ThreadPoolExecutor源码)

本文从ThreadPoolExecutor源码来理解线程池原理。 ThreadPoolExecutor使用了AQS、位操作、CAS操作等。在看这篇文章之前&#xff0c;需要具备以下知识&#xff1a; 多线程与高并发&#xff08;6&#xff09;——CAS详解&#xff08;包含ABA问题&#xff09; 多线程与高并发&…

腾讯三面:进程写文件过程中,进程崩溃了,文件数据会丢吗?

进程写文件&#xff08;使用缓冲 IO&#xff09;过程中&#xff0c;写一半的时候&#xff0c;进程发生了崩溃&#xff0c;会丢失数据吗&#xff1f; 答案&#xff0c;是不会的。 因为进程在执行 write &#xff08;使用缓冲 IO&#xff09;系统调用的时候&#xff0c;实际上是…

企业宣传片制作配音,我们该从哪里找?

优秀的品质的配音是制作优质企业视频必不可少的硬件条件。因此&#xff0c;许多公司视频配音或旁白声音是由专门从事配音行业的人员配音的。 首先是在宣传视频中配音的作用 1.宣传视频的配音为您建立企业形象 2.宣传视频的配音将为您打开市场 3.宣传视频的配音将使您的宣传…

深入理解Synchronized

Synchronized 底层原理 Synchronized的语义底层是通过一个 Monitor 的对象来完成&#xff0c;其实wait/notify等方法也依赖于 Monitor 对象&#xff0c;这就是为什么只有在同步的块中&#xff0c;拿到锁之后&#xff0c;才能调用wait/notify等方法&#xff0c;否则会抛出java.…

AI助力产品质量检验,基于YOLO实现瓷砖缺陷问题检测识别

在我之前的文章中也写过很多关于生产质检相关的实践文章&#xff0c;一直觉得这块是比较有意思的应用方向&#xff0c;做出来的模型能够以一种更加直观贴切的形式展现出来&#xff0c;瓷砖缺陷问题检测识别也是一个比较老的话题了&#xff0c;今天还是想拿出来具体实践做一下&a…

Golang.org/x库初探1——image库

Golang有一个很有意思的官方库&#xff0c;叫golang.org/x&#xff0c;x可能是extends&#xff0c;experimental&#xff0c;总之是一些在官方库中没有&#xff0c;但是又很有用的库。最近花点时间把这里有用的介绍一下。 Image库 提供更多的图像格式 golang.org/x/image库整…