【JavaEE】多线程安全问题

文章目录

  • 1、什么是多线程安全问题
  • 2、出现线程不安全的原因
    • 2.1 线程在系统中是随机调度,抢占式执行的
    • 2.2 多个线程同时修改同一个变量
    • 2.3 线程针对变量的修改操作,不是“原子”的
    • 2.4 内存可见性问题
    • 2.5 指令重排序
  • 3 、如何解决线程安全问题
    • 3.1 锁操作
    • 3.2 synchronized关键字
  • 4、不正确加锁引发的问题
    • 4.1 一个加锁一个不加锁
    • 4.2 可重入锁
    • 4.3 两个线程两把锁-死锁
    • 4.4 死锁的四个特性
      • 5.1 互斥特性
      • 5.2 不可抢占(不可被剥夺)
      • 5.3 请求和保持
      • 5.4 循环等待
    • 4.5 如何避免死锁


1、什么是多线程安全问题

线程是随机调度,抢占式执行,这样的随机性会使程序的执行顺序产生变数,从而产生不同的结果,但是有时候,遇到不同的结果,认为不可接收,认为是bug
多线程代码引起的bug,这样的问题就是“线程安全问题”存在“线程安全问题的代码”,就称为“线程不安全”
线程不安全的例子:

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

thread1和thread2都对count这个变量进行了++操作,所以我们预期的结果是10w,但是由于出现了线程不安全的问题所以这里输出的结果是小于10w的
注意:每次运行后的结果都是不一样的,大部分情况都是大于5w,但是也有一部分情况是小于5w的
在这里插入图片描述
具体说明:
count++这个代码其实是3个cpu指令
1.把内存count中的数值,读取到cpu寄存器中,我们取个名字叫load
2.把寄存器中的值+1,还是继续保存在寄存器中,取个名字叫add
3.把寄存器上述计算的值2,写回到内存count里,取个名字叫save
两个线程并发的进行count++,多线程的执行,是随机调度,抢占式的执行模式

综上所述在实际并发执行的时候,两个线程执行指令的相对顺序就可能会出现多种情况,不同的执行顺序,得到的结果也就可能会存在差异
在这里插入图片描述
第一种和第二种最终的结果都是正确的,第三种虽然t1和t2都执行了count++操作,但是t1将t2线程中的count进行了覆盖,重新赋值,所以t2线程的操作就是无效的
出现结果小于5w的情况:
在这里插入图片描述
注意:多个线程并发执行的时候,具体指令执行的先后顺序,可能存在无数种情况

2、出现线程不安全的原因

2.1 线程在系统中是随机调度,抢占式执行的

这个是线程不安全的罪魁祸首,万恶之源

2.2 多个线程同时修改同一个变量

一个线程修改同一个变量,没事
多个线程读取同一个变量,没事
多个线程修改不同变量,没事

2.3 线程针对变量的修改操作,不是“原子”的

“原子”指的是不可拆分的最小单位
count++这种不是原子操作,但是针对int/double进行赋值操作,在cpu中就是一个move指令

2.4 内存可见性问题

2.5 指令重排序

后面两个我们会在后面详细介绍

3 、如何解决线程安全问题

原因1:因为涉及到操作系统所以我们无法干预
原因2:这种做法在Java中不是很普适,只能针对一些特定的场景
原因3:是解决线程安全问题最普适的方法,我们可以通过加锁来解决线程安全问题

3.1 锁操作

关于锁操作的两个方面
1.加锁:t1加上锁之后,t2也尝试加锁,就会阻塞等待
2.解锁:直到t1解锁之后,t2才有可能拿到锁(加锁成功)
锁的主要特性:互斥
互斥指的是一个线程获取到锁之后,另一个线程也尝试加这个锁,就会阻塞等待(也叫锁竞争/锁冲突)
在代码中,我们可以创建多个锁,只有多个线程竞争同一把锁,才会产生互斥,针对不同的锁,则不会

3.2 synchronized关键字

synchronized关键字用于实现线程同步确保在同一时刻只有一个线程可以访问某个代码块或者方法
synchronized后面带上( ),括号里面写的就是“锁对象”
注意:锁对象的用途,有且只有一个,就是区分两个线程是否是针对同一个对象进行加锁,如果是就会出现锁竞争/锁冲突/互斥,就会引起阻塞等待,如果不是就不会出现锁竞争,也就不会阻塞等待
synchronized下面跟着{ },当进入到代码块就是给上述( )锁对象进行加锁操作,出代码块就是给上述( )锁对象进行解锁操作

public class Test19 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        //Java中随便拿一个对象,都可以做为加锁的对象
        Object locker1 = new Object();
        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                synchronized (locker1) {
                    count++;
                }
            }
        });
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                synchronized (locker1) {
                    count++;
                }
              
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count = " + count);
    }
}

运行结果:
在这里插入图片描述
此时我们通过加锁就得到了我们预期的结果

使用synchronized关键字修饰实例方法

class Counter {
    private int count = 0;
    synchronized public void add() {
        count++;
    }
    public int get() {
        return count;
    }
}
public class Test20 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count = " + counter.get());
    }
}

使用synchronized关键字修饰静态方法

class Counter {
    private static  int count = 0;
    synchronized public static void add() {
        count++;
    }
    public int get() {
        return count;
    }
}
public class Test20 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count = " + counter.get());
    }
}

4、不正确加锁引发的问题

4.1 一个加锁一个不加锁

public class Test19 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Object locker1 = new Object();
        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                synchronized (locker1) {
                    count++;
                }
            }
        });
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                    count++;
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count = " + count);
    }
}

运行结果:
在这里插入图片描述
观察结果发现这种情况同样会发生多线程不安全的问题
原因:thread2没加锁,意味着即使thread1加锁了,thread2执行过程中没有任何阻塞,没有互斥,仍然会使thread1 ++到一半的时候,被thread2进来把结果覆盖掉
所有当我们需要对两个线程中且操作同一个方法或者代码块,同时加锁才能解决多线程安全的问题

4.2 可重入锁

同一个线程中,对一个对象进行多次加锁就叫做可重入锁

public class Test19 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Object locker1 = new Object();
        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                synchronized (locker1) {
                    synchronized (locker1) {
                        count++;
                    }
                }
            }
        });
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                synchronized (locker1) {
                    count++;
                }

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

假设thread1对代码块进行加锁,那么thread2就不能进行加锁操作,此时就会发生锁冲突,只有thread1解锁之后,threadd2才能进行加锁操作。对于thread1来说外层加完锁之后,此时内层在加锁之前就会判断当前是哪个线程对当前的代码块进行的加锁,如果是当前线程就会无视内层加锁这个操作继续往下执行,如果不是就会阻塞等待
所有当前运行结果是正确的:
在这里插入图片描述
注意:当加了多层锁的时候,代码要执行到最外层 } 花括号才会自动解锁,而不是内层的 } 括号解锁,所有内层的加锁是没用的,在最外层加锁就足够了

4.3 两个线程两把锁-死锁

死锁:两个或者多个线程(或进程)相互等待对方释放资源而造成的一种僵局,导致代码无法正常执行

public class Test21 {
    public static void main(String[] args) throws InterruptedException {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread thread1 = new Thread(()-> {
            synchronized (locker1) {
                try {
                    //引用sleep是为了更好的控制线程执行的顺序
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker2) {
                    System.out.println("thread1获取到两把锁");
                }
            }

        });
        Thread thread2 = new Thread(()-> {
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker1) {
                    System.out.println("thread2获取到两把锁");
                }
            }

        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
    }
}

在这里插入图片描述
在这里插入图片描述
运行结果:
在这里插入图片描述

4.4 死锁的四个特性

5.1 互斥特性

一个线程拿到锁之后,其他线程就得阻塞等待

5.2 不可抢占(不可被剥夺)

一个线程拿到锁之后,除非它自己主动释放锁,否则别人抢不走

5.3 请求和保持

一个线程拿到一把锁之后,不释放这个锁的前提下,再尝试获取其他锁

5.4 循环等待

多个线程获取多个锁的过程中,出现了循环等待,A等B,B又等A

4.5 如何避免死锁

1.锁具有互斥特性
2.锁不可抢占(不可被剥夺)
前面两种可以自己实现锁来打破,但是对于synchronized这样的锁是不行的
3.请求和保持,打破方法是不要让锁嵌套获取
4.循环等待,打破循环等待,即使出现嵌套也不会死锁,约定好加锁的顺序让所有的线程按照固定的顺序来获取锁

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

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

相关文章

2024年3月Scratch图形化编程等级考试(三级)真题试卷

2024年3月Scratch图形化编程等级考试&#xff08;三级&#xff09;真题试卷 选择题 第 1 题 Scratch运行程序后&#xff0c;角色一定不会说出的数字是&#xff1f;&#xff08; &#xff09; A.2 B.4 C.6 D.8 第 2 题 Scratch角色初始位置如下图所示&#xff0c;右图…

14_Scala面向对象编程_属性

属性 1.类中属性声明 // 1.给Scala声明属性&#xff1b;var name :String "zhangsan"val age :Int 302.系统默认赋值 scala由于初始化变量必须赋值&#xff0c;为了解决此问题可以采用下划线赋值&#xff0c;表示系统默认赋值 , –但是此方法局限于变量&…

ArkTS开发原生鸿蒙HarmonyOS短视频应用

HarmonyOS实战课程“2024鸿蒙零基础快速实战-仿抖音App开发&#xff08;ArkTS版&#xff09;”已经于今日上线至慕课网&#xff08;https://coding.imooc.com/class/843.html&#xff09;&#xff0c;有致力于鸿蒙生态开发的同学们可以关注一下。 课程简介 本课程以原生鸿蒙Ha…

Hibernate执行流程分析及配置文详解

目录 1、Hibernate执行流程分析及配置文件详解 1&#xff09;Configuration对象 2&#xff09;ServiceRegistry对象&#xff08;hibernate4的新特性&#xff09; 3&#xff09;SessionFactory对象 4&#xff09;Session对象 5&#xff09;Transaction对象 6&#xff09;…

缓冲流,BufferReader,BufferWriter,案例

IO流的体系 字节缓冲流的作用 提高字节流读取数据的性能 *原理&#xff1a;字节缓冲输入流自带了8Kb的缓冲池&#xff0c;字节缓冲输出流也自带了8kb的缓冲池 构造器说明public BufferedInputStream(InputStream is)把低级的字节输入流包装成一个高级的缓冲字节输入流&#…

对链表进行插入排序(详细解析)

对链表进行插入排序&#xff08;详解&#xff09; 题目&#xff1a; 对链表进行插入排序 给定单个链表的头 head &#xff0c;使用 插入排序 对链表进行排序&#xff0c;并返回 排序后链表的头 。 插入排序 算法的步骤: 插入排序是迭代的&#xff0c;每次只移动一个元素&a…

特斯拉FSD落地分析

再续前缘 媒体的神经从马斯克的湾流私人飞机起飞那一刻开始,就开始被牵动着。28/4 号的突然访华,在大多数人看来其实已经早已是计划之中,从摆在台面上的消息来看,主要目的是为了在大陆推广FSD的落地,也为8月份FSD 的正式版本做预热,和中国上海的第一次联姻造就了特斯拉m…

17 内核开发-内核内部内联汇编学习

​ 17 内核开发-内核内部内联汇编学习 课程简介&#xff1a; Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础&#xff0c;让他们能够理解和参与到Linux内核的开发过程中…

【Linux】进程exec函数族以及守护进程

一.exec函数族 1.exec函数族的应用 在shell下敲shell的命令都是在创建shell的子进程。而我们之前学的创建父进程和子进程代码内容以及通过pid与0的关系来让父子进程执行不同的代码内容都是在一个代码文件里面&#xff0c;而shell是如何做到不在一个文件里面写代码使之成为子进…

06|LangChain | 从入门到实战 -六大组件之Agent

点点赞~ 注意&#xff1a;langchain的版本迭代比较快&#xff0c;社区维护&#xff0c;代码当中或许部分方法在某个版本不再支持 01&#xff5c;LangChain | 从入门到实战-介绍 02&#xff5c;LangChain | 从入门到实战 -六大组件之Models IO 03&#xff5c;LangChain | 从入…

asp.net结课作业中遇到的问题解决2

目录 1、如何实现评论交流的界面 2、如果想要将文字添加到数据库中&#xff0c;而不是乱码&#xff0c;该怎么修改 3、如果想要添加的数据已经存在于数据库&#xff0c;就不允许添加了&#xff0c;该如何实现 4、想要实现某个模块下有好几个小的功能该如何实现 5、想要实现…

代码随想录算法训练营第25天 | 216.组合总和III、17.电话号码的字母组合

代码随想录算法训练营第25天 | 216.组合总和III、17.电话号码的字母组合 自己看到题目的第一想法看完代码随想录之后的想法 链接: 216.组合总和III 链接: 17.电话号码的字母组合 自己看到题目的第一想法 216.组合总和III&#xff1a;递归函数终止条件为搜索得到的数相加为n&…

【架构系列】RabbitMQ应用场景及在实际项目中如何搭建可靠的RabbitMQ架构体系

作者:后端小肥肠 创作不易&#xff0c;未经允许禁止转载。 1. 前言 RabbitMQ&#xff0c;作为一款高性能、可靠的消息队列软件&#xff0c;已经成为许多企业和开发团队的首选之一。它的灵活性和可扩展性使得它适用于各种应用场景&#xff0c;从简单的任务队列到复杂的分布式系统…

阿里低代码引擎学习记录

官网 一、关于设计器 1、从设计器入手进行低代码开发 设计器就是我们用拖拉拽的方法&#xff0c;配合少量代码进行页面或者应用开发的在线工具。 阿里官方提供了以下八个不同类型的设计器Demo&#xff1a; 综合场景Demo&#xff08;各项能力相对完整&#xff0c;使用Fusion…

【前端项目——分页器】手写分页器实现(JS / React)

组件介绍 用了两种方式实现&#xff0c;注释详细~ 可能代码写的不够简洁&#xff0c;见谅&#x1f641; 1. 包含内容显示的分页器 网上看了很多实现&#xff0c;很多只有分页器部分&#xff0c;没和内容显示联动。 因此我增加了模拟content的显示&#xff0c;这里模拟了32条数…

Python数据分析案例44——基于模态分解和深度学习的电负荷量预测(VMD+BiGRU+注意力)

案例背景 承接之前的案例&#xff0c;说要做模态分解加神经网络的模型的&#xff0c;前面纯神经网络的缝合模型参考数据分析案例41和数据分析案例42。 虽然我自己基于各种循环神经网络做时间序列的预测已经做烂了.....但是还是会有很多刚读研究生或者是别的领域过来的小白来问…

Monorepo(单体仓库)与MultiRepo(多仓库): Monorepo 单体仓库开发策略与实践指南

&#x1f31f; 引言 在软件开发的浩瀚宇宙里&#xff0c;选择合适的代码管理方式是构建高效开发环境的关键一步。今天&#xff0c;我们将深入探讨两大策略——Monorepo&#xff08;单体仓库&#xff09;与MultiRepo&#xff08;多仓库&#xff09;&#xff0c;并通过使用现代化…

Vue3 + Vite + TypeScript + Element-Plus创建管理系统项目

官方文档 Vue3官网 Vite官方中文文档 创建项目 使用npm命令创建项目&#xff1a; npm create vitelatest输入项目名称&#xff1a; ? Project name:项目名选择vue&#xff1a; ? Select a framework: - Use arrow-keys. Return to submit.Vanilla > VueReactPrea…

【网站项目】木里风景文化管理平台

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

CSS精灵图、字体图标、HTML5新增属性、界面样式和网站 favicon 图标

精灵图 为什么要使用精灵图 一个网页中往往会应用很多小的背景图像作为修饰&#xff0c;当网页中的图像过多时&#xff0c;服务器就会频繁地接收和发送请求图片&#xff0c;造成服务器请求压力过大&#xff0c;这将大大降低页面的加载速度,因此&#xff0c;为了有效地减少服务…