【后端开发】JavaEE初阶—线程安全问题与加锁原理(超详解)

前言:

🌈上期博客:【后端开发】JavaEE初阶—Theard类及常见方法—线程的操作(超详解)-CSDN博客

🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客

🌈小编会在后端开发的学习中不断更新~~~

 

目录

📚️1.引言

📚️2.线程状态

📚️3.线程安全

3.1什么是线程安全

3.2线程安全问题原理

3.3线程安全问题原因

📚️4.实现加锁

4.1加锁的原因

4.2加锁的目的

 4.3加锁的实现

5.加锁的注意事项

6.加锁的其他写法

 📚️5.总结


📚️1.引言

 Hello!!!小伙伴们,小编上期讲解了关于Tread类的相关知识解析,以及对于线程的相关操作,相信大家对于这类知识有了新的理解,本期将讲解关于线程的重点问题,即关于线程安全和加锁的理解;开始发车了gogogo~~~🥳🥳🥳;

且听小编讲解,包你学会!!! 

📚️2.线程状态

关于线程状态,我们之前讲解到,线程大致有两种状态

就绪状态:表示这个线程随时可以实现调度去CPU上执行,并且包括已经在CPU上执行的线程;

阻塞状态:即这个线程不方便去CPU上进行执行,即不方便调度,并且在java中对于阻塞有几种状态;

几种状态:

NEW:表示Thread对象创建后,但是还没有调用start方法在系统内创建线程

RUNNABLE:表示线程进入就绪状态,线程准备被CPU调度,或者已经在CPU上执行

TERMINATED:表示系统内的线程已经被执行了,线程已经销毁,但是Thread类对象还存在
TIMED_WAITING:指时间阻塞,到达一定的时间后解除阻塞,主要是在Sleep的休眠等待阻塞

WAITING:即不带有时间的阻塞(死等),只有达到一定的条件后,才会解除阻塞,主要是join,wait;

BLOCKED:即产生的锁竞争,引起的阻塞;后面现编会讲到

以上小编WAITING和BLOCKED小编就后面介绍

对于上述的概念比较难懂,那么接下来,小编用代码为大家进行实现,讲解吧~~~

代码实现:

public static void main(String[] args) throws InterruptedException {Thread thread=new Thread(()->{System.out.println("线程执行中");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}});System.out.println("线程状态"+ thread.getState());//NEWthread.start();System.out.println("线程状态"+ thread.getState());//RUNNABLEThread.sleep(2000);//确保线程执行完System.out.println("线程状态"+thread.getState());//TERMINATED}

讲解:线程start方法之前,线程还没有被创建,所以这里就是NEW,在线程创建后,此时线程就为就绪状态,此时就是RUNNABLE,执行完后,就是TERMINATED;

当然那还有TIMED_WAITING状态,代码如下:

thread.start();
Thread.sleep(2000);//确保线程进入休眠
System.out.println("线程状态"+thread.getState());//TIMED_WAITING

讲解:这里进行主线程的休眠是为了确保线程进入休眠等待状态,此时状态就TIMED_WAITING

 图解实例:

注意:红色框里为线程的一般执行过程,若在加入了一些相关指令,没那么对应的线程状态也要进行改变;

📚️3.线程安全

实现多线程编程是为了实现并发线程,但是实现并发编程并不只有多线程才能实现~~~

多线程编程是比较原始,但是比较朴素的一种实现并发编程的方法

3.1什么是线程安全

当一个任务在单线程的执行下,或者在多个线程的执行下,没有出现BUG的情况下,那么就是线程安全的;

如果一个任务在单线程的执行下是没有BUG的,但是在多个线程的执行下又出现了BUG,那么此时就是线程不安全的;

 例如如下代码:

 public static int num=0;//线程安全问题public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for (int i = 0; i <50000 ; i++) {            num++;}});Thread t2=new Thread(()->{for (int i = 0; i <50000 ; i++) {               num++;               }});t1.start();t2.start();t1.join();t2.join();System.out.println("num="+num);}

注解:可以看到,我们这里实现的两个线程来进行num的递增,当两个线程启动后,我们的预料输出就是10万,但是输出如下:

此时可以看到这里和我们的预期输出是不一样的,那么这是为什么呢???

3.2线程安全问题原理

在上述问题中,我们发现此时在实现变量自增时,此时的就发生了线程的安全问题~~~

那么此时我们就要从CPU指令原理来理解了:

注意:这个执行语句num++,实际是由cpu上的三个指令构成的;

📍load:表示从内存中读取数据到cpu寄存器中;

📍add:表示把寄存器中的值加一;

📍save:表示将寄存器的数据写回到内存当中;

 又因为线程的随机调度的问题,此时就有一下几种情况:

第一种情况;

那么此时我们就可以看到,这里的执行结果就符合我们数值递增的规律,那么此时又因为线程的随机调度,和抢占式执行,那么大多数就是按照以下情况执行:

第二种情况:

  

通过上述的图片解析,我们有以下了解:

1.在多线程实现时,存在随机调度的问题,那么此时cpu的三个指令会随机调度到cpu上去执行,此时就存在线程安全问题;

2.在随机调度的上述两中情况是不一定的,还存在无数种;

3.线程安全的前提是第一个线程成功save数据回到内存中后,线程二再load读取数据后,才能保证线程安全,但是这是几率很小的,大多数都是第二种类似的情况

由于小编画图有限,这里的第二种情况还有很多,大家可以自己试试边画边理解哦;

3.3线程安全问题原因

在上述的总结实现后,小编总结了一下三点问题;

📍(根本原因)由于线程的随机调度,和抢占式执行的,导致了线程之间执行的顺序是不确定的,是有无数种情况的;

📍(代码结构)即多个线程同时执行一个任务,那么此时就会存在线程安全问题

📍(直接原因)上述的num++操作本来就不是“原子的”,即最小执行单元,要么不执行,要么执行完

📍还有内存可见性以及指令重排序的问题,这里代码没有涉及,小编就不再讲解了(小编也不知道😁😁😁)

📚️4.实现加锁

4.1加锁的原因

在上述讲解中,我们了解到线程的安全问题就是由于线程随机调度,导致的执行顺序不确定;

那么对于上述的几种原因,我们能够控制的就是(代码结构)(直接原因)但是为了实现这种代码结构,就会切断提高执行效率的要求,所以我们只能从直接原因入手;

4.2加锁的目的

加锁的目的就是为了实现多个指令,打包成一个原子的操作;

加锁后线程任然要进行随机的调度,但是此时即时在执行的线程被调度走了,但是其他线程仍然不会插队进行执行;

如图:

注意:这里的lock和unlock实现加锁后,添加的两条指令,在java中使用synchronized即可实现;

此时就解决了线程的随机调度和抢占式执行产生的线程安全的问题了~~~

 4.3加锁的实现

当我们要实现加锁的操作时,那么就要添加锁对象,加锁解锁的操作都是依靠这个锁对象来执行的

 代码实现如下:

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

此时可以发现,小编设置了一个Objec类的对象,这里只要是对象就都可以,运用synchronized实现对于要进行加锁代码的操作;

注意:如果一个线程针对一个锁对象进行加锁后,其他线程也针对这个锁对象进行加锁,就会发生阻塞(BLOCKED)直到前一个线程执行完后即释放锁

5.加锁的注意事项

1.当线程加锁,另一个线程不加锁的时候

Thread t1=new Thread(()->{for (int i = 0; i <50000 ; i++) {加锁synchronized (ram){num++;}}});Thread t2=new Thread(()->{for (int i = 0; i <50000 ; i++) {                num++;           }});

这里就是一个加锁,另一个不加锁的情况,此时仍然存在线程安全问题;

注意:当写成上述情况时,此时两个线程就不会存在锁竞争了,那么对应的也就没有了线程阻塞问题,此时仍然会因为线程调度,导致线程安全问题;

2,当加锁的对象不同

Thread t1=new Thread(()->{for (int i = 0; i <50000 ; i++) {synchronized (ram){num++;}}});Thread t2=new Thread(()->{for (int i = 0; i <50000 ; i++) {synchronized (tam) {num++;}}});

那么此时和上述情况一样,不会存在锁竞争,不会产生阻塞;

3.在加锁时要保证加锁对象一样

除了上述的写法之外,我们还能够用方法来实现:

 public static void main(String[] args) throws InterruptedException {Test t=new Test();Thread t1=new Thread(()->{for (int i = 0; i <50000 ; i++) {t.add();}});Thread t2=new Thread(()->{for (int i = 0; i <50000 ; i++) {t.add();}});//线程启动,与等待}
}
class Test{public int num=0;public void add(){synchronized (this){num++;}}

小编这里省去了线程的启动和等待哦~~~

这里就运用了方法实现线程的加锁,这里的this代表的是创建这个类的对象实例,那么此时两个线程调用方法的对象一样,那么此时也是线程安全的;

注意:综上所述,在解决线程安全问题时,主要是实现线程阻塞,加锁;那么加锁的对象一定要是一致的,否则会导致无法产生线程锁竞争,从而产生线程安全问题

6.加锁的其他写法

在实现加锁的操作时,对于以上的加锁方式,我们可以实现改进;

这里synchronized(this)代码中,我们就可以将synchronized写到方法上,代码如下:

class Test {public int num = 0;synchronized public void add() {num++;}
}

这种写法和上述的写法作用效果是一样的;

对于当括号里面是类对象时:

class Test {public int num = 0;public  void add() {synchronized (Test.class){num++;}}
}

注意:在一个Java进程中只有一个类对象,所以第一个进程拿到的类对象和第二个进程拿到的类对象是同一个对象,此时就满足阻塞的要求;

当synchronized修饰静态方法时:

class Test {public static int num = 0;public static void add() {synchronized (Test.class){num++;}}
}

 这个写法可以被代替成:

synchronized public static void add() {num++;}

注意:如果synchronized是加在static静态方法上就相当于给类对象加锁;

 📚️5.总结

💬💬本期小编总结了关于多线程编程的重要问题,线程状态,以及线程安全问题讲解,并且如何解决线程安全问题,进行了原理讲解,并提出加锁的概念和实现;还附上了代码加以讲解~~~

🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!


💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。

                                                               😊😊  期待你的关注~~~

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java反射机制入门:解锁运行时类信息的秘密

反射技术&#xff1a; 其实就是对类进行解剖的技术 类中有什么&#xff1f;构造方法 成员方法成员变量 结论&#xff1a;反射技术就是把一个类进行了解剖&#xff0c;然后获取到 构造方法、成员变量、成员方法 反射技术的应用案例&#xff1a; idea框架技术&#xff1a;Spr…

通过document获取节点元素

1.层级节点 <ul><li id"li1">1</li><li>2</li><li id"li3">3</li><li>4</li><li>5</li></ul><script>//获取id名为li1的元素赋值给li1let li1document.getElementById(li…

爬虫----webpack

目录 一. 什么是webpack 出现的原因&#xff1a;同名函数 概念: 特征&#xff1a;大量缩进 webpack的格式 简单的webpack格式&#xff1a; 详细的webpack格式&#xff1a; 几个参数的运用 1. webpack数组形式 2. webpack对象格式 3.多个js文件打包 打印要扣的代码 …

【chromedriver编译-绕过selenium机器人检测】

有小伙伴说使用selenium没能绕过机器人检测&#xff0c;盘他。 selenium机器人检测有2种&#xff0c;一是cdp检测&#xff0c;二是webdriver特征检测。cdp检测前面的博客已写过&#xff0c;这里就提下webdriver特征检测。一、selenium简介 Selenium 是一个强大的工具&#xff…

单片机带隙电压基准电路

单片机带隙电压基准电路 一、带隙电压基准电路概述 带隙电压基准电路在单片机中占据着至关重要的地位。它能够为各种模拟集成电路提供稳定的参考电压&#xff0c;确保电路的正常运行。例如&#xff0c;在高精度的比较器中&#xff0c;带隙电压基准电路可以提供一个精确的参考…

linux 的 sed 命令的 使用学习

&#xff08;1&#xff09; sed 概述&#xff1a; &#xff08;2&#xff09; 首先谢谢 b 站这位老师&#xff0c;这位专家的完美讲解 讲解继续&#xff1a; &#xff08;3&#xff09; 关于 sed 里的模式&#xff1a; &#xff08;4&#xff09; sed 支持的常用的对文本编辑的…

Matlab|考虑柔性负荷的综合能源系统低碳经济优化调度

目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 程序主要实现的是考虑柔性负荷的综合能源系统低碳经济优化调度&#xff0c;模型参考《考虑柔性负荷的综合能源系统低碳经济优化调度》&#xff0c;求解方法采用的是混合整数规划算法&#xff0c;通过matlabc…

【设计模式】UML类图

目录 前言 一、类图概述 二、类图的作用 三、类图表示法 四、类之间关系的表示方法 1. 关联关系 1.1 单向关联 1.2 双向关联 1.3 自关联 2. 聚合关系 3. 组合关系 4. 依赖关系 5. 继承关系 6. 实现关系 总结 前言 统一建模语言&#xff08; Unified Modeling La…

如何快速上手一个Github的开源项目

程序研发领域正是有一些热衷开源的小伙伴&#xff0c;技能迭代才能如此的迅速&#xff0c;因此&#xff0c;快速上手一个GitHub上的开源项目&#xff0c;基本上已经变成很个程序员小伙伴必须掌握的技能&#xff0c;因为终究你会应用到其中的一个或多个项目&#xff0c;帮助自己…

【资源一号04A卫星(中巴地球资源卫星04A星)】

资源一号04A卫星&#xff08;中巴地球资源卫星04A星&#xff09; 资源一号04A卫星&#xff0c;全称为中巴地球资源卫星04A星&#xff08;CBERS-04A&#xff09;&#xff0c;是中国与巴西两国合作研制的第六颗地球资源卫星。以下是对该卫星的详细介绍&#xff1a; 一、基本信…

打造灵活DateTimePicker日期时间选择器组件:轻松实现时间的独立清除功能

element ui中日期和时间选择器&#xff08;DateTimePicker&#xff09;是一个常见且重要的组件。它允许用户轻松地选择日期和时间&#xff0c;极大地提升了用户体验。然而&#xff0c;在某些场景下&#xff0c;用户可能需要更细粒度的控制&#xff0c;例如单独清除已选择的时间…

【资源一号02C卫星】

资源一号02C卫星 资源一号02C卫星是中国航天科技集团公司所属中国空间技术研究院负责研制生产的一颗重要遥感卫星。以下是关于该卫星的详细介绍&#xff1a; 一、基本信息 发射时间&#xff1a;2011年12月22日11时26分发射地点&#xff1a;中国太原卫星发射中心运载火箭&am…

基于区块链的相亲交易系统源码解析

随着区块链技术的成熟与发展&#xff0c;其去中心化、不可篡改的特性逐渐被应用于各行各业。特别是在婚恋市场中&#xff0c;区块链技术的应用为相亲平台带来了新的可能性 。本文将探讨如何利用区块链技术构建一个透明、高效的相亲交易系统&#xff0c;并提供部分源码示例。 区…

提前解锁 Vue 3.5 的新特性

Vue 3.5 是 Vue.js 新发布的版本&#xff0c;虽然没有引入重大变更&#xff0c;但带来了许多实用的增强功能、内部优化和性能改进。 1. 响应式系统优化 Vue 3.5 进一步优化了响应式系统的性能&#xff0c;并且减少内存占用。尤其在处理大型或深度嵌套的响应式数组时&#xff…

Contact Form 7最新5.9.8版错误修复方案

最近有多位用户反应Contact Form 7最新5.9.8版的管理页面有错误如下图所示 具体错误文件的路径为wp-content\plugins\contact-form-7\admin\includes\welcome-panel.php on line 153 找到welcome-panel.php这个文件编辑它&#xff0c;将如下图选中的部分删除 删除以后&#xf…

显示和隐藏图片【JavaScript】

使用 JavaScript 来实现显示和隐藏图片。下面是一个简单的示例&#xff0c;展示如何通过按钮点击来切换图片的可见性。 实现效果: 代码&#xff1a; <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name&…

python爬虫案例——抓取链家租房信息

文章目录 1、任务目标2、分析网页3、编写代码1、任务目标 目标站点:链家租房版块(https://bj.lianjia.com/zufang/) 要求:抓取该链接下前5页所有的租房信息,包括:标题、详情信息、详情链接、价格 如: 2、分析网页 用浏览器打开链接,按F12或右键检查,进入开发者模式;因…

计算机毕业设计 美发管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

OpenAI converting API code from GPT-3 to chatGPT-3.5

题意&#xff1a;将OpenAI API代码从GPT-3转换为ChatGPT-3.5 问题背景&#xff1a; Below is my working code for the GPT-3 API. I am having trouble converting it to work with chatGPT-3.5. 以下是我用于GPT-3 API的工作代码。我在将其转换为适用于ChatGPT-3.5时遇到了…

前端开发之装饰器模式

介绍 装饰器模式 是在不修改对象内部结构的情况下&#xff0c;动态地给对象添加功能的一种设计模式。在软件开发中&#xff0c;有时候我们需要为已有对象添加一些额外的行为&#xff0c;但不希望修改该对象的代码&#xff0c;装饰器模式可以很好的满足这一需求。 在TypeScrip…