【Java多线程编程】解决线程的不安全问题

news2024/11/23 18:49:11

前言: 

当我们进行多线程编程时候,多个线程抢占系统资源就会造成程序运行后达不到想要的需求。我们可以通过 synchronized 关键字对某个代码块或操作进行加锁。这样就能达到多个线程安全的执行,因此我把如何使用 synchronized 进行加锁的操作过程分享给大家。

目录

1. 线程的不安全原因

1.1 原子性

1.2 解决线程不安全问题

2. synchronized关键字

2.1 synchronized的参数

2.2 synchronized的范围


1. 线程的不安全原因


1.1 原子性

何为原子性,就是唯一的不可分割的

举个例子,有一厕所,三个线程想要上这个厕所。但是这个厕所只有一个,线程1进去了那么线程2和线程3就不得进入这个厕所了。

如果线程1进入厕所后,线程2和线程3强行进入厕所使得线程1不能好好上厕所这样就没有隐私可言,相对来说也是没有原子性。

因此,我们可以把厕所上个锁,使得厕所只能进入一个人,这样也就具备了原子性。


一条 Java 语句并不总是原子性的,如一个 count++ 语句。它在 cpu 中存在三个步骤:

  1. 从内存把数据读到 CPU
  2. 进行数据更新
  3. 把数据写回到 CPU

上述 Java 语句。如果一个线程正在对进行 count++ 操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的,也是不具备原子性。

但这个条 Java 语句通过上锁的形式,就能达到原子性。如何上锁在下方有讲解。


案例:

当多个线程运行时,就会导致线程的不安全。其原因在于各个线程会抢占资源。比如以下代码:

//创建一个自定义类
class myThread {
    int count = 0;
    public void run() {
        count++;
    }
    public int getCount() {
        return count;
    }
}

public class TreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        myThread myThread = new myThread();//实例化这个类
        
        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });
        
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });

        thread1.start();//启动线程thread1
        thread2.start();//启动线程thread2

        thread1.join();//等待线程thread1结束
        thread2.join();//等待线程thread2结束

        System.out.println(myThread.getCount());//获取count值
    }
}

运行后输出:

以上代码的需求为:两个线程分别计算10000 次,使得 count 总数达到 20000。但实际运行后输出的值一个小于 20000 的数字,也可以说输出的值是一个小于 20000 的随机值。因此,我们可以认为这就是线程的不安全。

在以上代码中 count++ 这个操作它是由 cpu 的三个指令构成的:

  • load,把内存中的数据读取到 cpu 寄存器中。
  • add,把寄存器中的值进行++运算
  • save,把寄存器中的值写回到内存中

因此,当线程 thread1 在进行 count++ 操作时,thread2 也进行 count++ 操作。这样就会导致thread1 线程未执行完毕就执行 thread2 线程了,这样的两个线程就是不安全的线程。

上图中 thread1线程 进行 count++ 操作, load 指令把 1 加载到内存中。

thread2 线程未等thread1 线程 add、save 操作执行完毕,则进行了 count++ 操作。

因此 thread1 中的 load指令 被threa2 中的 load指令 覆盖了。

原本 count++ 操作需要执行两次,thread2 线程抢占 thread1 线程导致 count++ 操作相当于只执行一次。


1.2 解决线程不安全问题

上述1.1中的几个例子,我们了解到多线程操作时候,线程之前会抢占资源造成线程不安全问题。

因此,我们可以通过加锁的形式使线程不得随意抢占资源。加锁是通过 synchronized 关键字进行加锁,请看下方讲解。


2. synchronized关键字

synchronized 关键字就是给线程加锁,当给线程进行加锁后,这个线程就能安全的运行。把上文中的线程进行加锁:

//创建一个自定义类
class myThread {
    int count = 0;
    public void run() {
        synchronized (this){
            count++;
        }
    }
    public int getCount() {
        return count;
    }
}
public class TreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        myThread myThread = new myThread();//实例化这个类

        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });
        thread1.start();//启动线程thread1
        thread2.start();//启动线程thread2
        thread1.join();//等待线程thread1结束
        thread2.join();//等待线程thread2结束
        System.out.println(myThread.getCount());//获取count值
    }
}

运行后输出:

以上代码输出20000,达到了上文中代码的需求。


2.1 synchronized的参数

synchronized 括号里面参数为当前线程的引用,也就是什么引用调用了 synchronized 关键。那么 synchronized 就为这个引用(线程)里面的代码块或操作进行加锁。

        synchronized (this) {
            count++;
        }

对应上图进行理解。

注意,当多个线程对一个对象进行加锁时,此时就会出现“锁竞争”。也就是一个线程拿到了锁,其他线程处于等待(阻塞)状态。类似于本文中的代码。

当多个线程对多个对象进行加锁时,就不会出现“锁竞争”也就是抢占资源的线性,各自加锁各自的线程即可。


2.2 synchronized的范围

synchronized 关键字触发锁是从代码块 {} 开始,出了 {} 锁结束。

注意,这个线程的里面的锁失效后,其他线程也会抢占这个锁。哪个线程先抢占到这个锁,就先进行锁的约束范围。其他未抢占到锁的线程也是处于等待(阻塞)状态。

举个例子,一个厕所,三个线程想要上这个厕所。当线程1上完厕所后,线程2和线程3会抢占这个厕所。因此,锁也像资源一样会被抢占。


综上所述,我们可以通过 synchronized 关键字来对线程中的某个代码块进行加锁。这样就能保证程序正在正常的运行,也就解决了线程的不安全问题。当然,我更新了关于 synchronized 关键字详细介绍,大家可以在下方专栏中查阅。 

🧑‍💻作者:程序猿爱打拳,Java领域新星创作者,阿里云社区优质创造者。

🗃️文章收录于:Java多线程编程

🗂️JavaSE的学习:JavaSE

🗂️Java数据结构:数据结构与算法

 本篇文章到这里就结束了,感谢点赞、关注、收藏~

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

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

相关文章

ChatGPT插件推荐,效率提升100倍!

在浏览器上使用ChatGPT时&#xff0c;借助一些插件可以帮助我们更便捷的获取消息&#xff0c;比如&#xff1a; 在搜索引擎搜索东西的同时和ChatGPT对话&#xff1b; 同一个问题同时向ChatGPT、newBing、Claude 等多个模型提问获取结果&#xff1b; 让ChatGPT可以联网获取最新…

实时聊天如何做,让客户眼前一亮(一)

网站上的实时聊天功能应该非常有用&#xff0c;因为它允许客户支持立即帮助用户。在线实时聊天可以快速轻松地访问客户服务部门&#xff0c;而它也代表着企业的门面。 让我们讨论一下如何利用SaleSmartly&#xff08;ss客服&#xff09;在网站中的实时聊天视图如何提供出色的实…

Yolov8改进:小目标到大目标一网打尽,轻骨干重Neck的轻量级目标检测器GiraffeDet

1.GiraffeDet介绍 论文:https://arxiv.org/abs/2202.04256 🏆🏆🏆🏆🏆🏆Yolov8魔术师🏆🏆🏆🏆🏆🏆 ✨✨✨魔改网络、复现前沿论文,组合优化创新 🚀🚀🚀小目标、遮挡物、难样本性能提升 🍉🍉🍉定期更新不同数据集涨点情况 本文是阿里巴…

【Autoware】Open Planner代码分析

目录 包结构op_global_plannerop_global_planner_core.cpp中代码的主要逻辑 op_local_plannerop_trajectory_generatorop_behavior_selectorop_common_paramsop_motion_predictorop_trajectory_evaluator 本篇主要对Open Planner的代码进行分析&#xff0c;主要包括op_global_p…

FSS对象存储挂载到windows云服务器操作方法

FSS对象存储可以挂载到云主机中用于存储视频、备份等不需要 经常读写的大文件。不适合存放数据库等对IO需求较高、经常读写的场景。 1、远程登陆服务器&#xff0c;打开控制面板&#xff0c;然后点击“打开或关闭windows功能”。 windows2008系统&#xff1a; 选择“功能”-- …

图可视化工具Gephi使用教程

图可视化工具Gephi使用教程 操作界面介绍在Gephi界面完成图的绘制键盘输入导入CSV文件直接在概览界面鼠标点击创建自己创建一个红楼梦关系网络图用一个Web of Science上的数据创建一个有向关系图 静态随机数据使用动态数据的使用Gephi的可视化处理节点移动节点放大&缩小单个…

【JAVA程序设计】(C00133)基于SSM的勤工助学管理系统

基于SSM的勤工助学管理系统 项目简介项目获取开发环境项目技术运行截图 项目简介 本系统为基于SSM的学生勤工助学俭学管理系统&#xff0c;本系统分为三种角色&#xff1a;管理员、学生、用人单位&#xff1b; 管理员功能&#xff1a;个人中心、面试邀请管理、岗位报名管理、…

二十三:创建交易

功能需求&#xff1a; 用户在交易主页面&#xff0c;点击”创建”按钮&#xff0c;跳转到创建交易的页面&#xff1b; 用户在创建交易的页面填写表单&#xff0c;点击”保存”按钮&#xff0c;完成创建交易的功能。 *所有者、阶段、类型、来源 都是动态的 *市场活动源是可搜…

《编程思维与实践》1061.计算n!右端0的个数(II)

《编程思维与实践》1061.计算n!右端0的个数(II) 题目 思路 可以用大整数的乘法来处理,不过有些题目可以用数论的方法得到更方便的做法, 以本题为例: 注意到 10 2 ⋅ 5 102\cdot 5 102⋅5 , 所以只需要将阶乘的每一项分解成2和5的乘积形式即可, 同时由于每出现一个5,必然会出现…

shell补充命令

目录 排序sort命令 语法格式: 常用选项: 去重uniq命令 替换tr命令 替换字符 删除​编辑 压缩​编辑 压缩替换​编辑 删除空行 截取cut命令 拆分split命令 合并paste命令​ eval命令 排序sort命令 以行为单位对文件内容进行排序&#xff0c;也可以根据不同的数据类型…

淘天集团首战618,一场平台生态的“再创业”

电商行业还在“卷”&#xff0c;但不是所有人都还把目光放在低价、噱头上。 5月10日&#xff0c;淘宝天猫618商家大会应时而至&#xff0c;随之而来的&#xff0c;是“淘天集团”的首次公开亮相。淘天集团CEO戴珊表示&#xff0c;“新生变革”、自我变革不是选择题而是必答题。…

4.实现csdn暗黑模式-油猴脚本实战1

4.实现csdn暗黑模式-油猴脚本实战1 Start 前面的文章&#xff0c;对油猴脚本做了基础的介绍。今天来我们来实战一下&#xff0c;编写一个有实际作用的油猴脚本。 如果我希望我的csdn是暗黑模式&#xff0c;怎么办&#xff1f; 看我表演&#xff1f; 注意&#xff01;本文仅用…

shell脚本----正则表达式

文章目录 一、什么是正则表达式二、正则表达式的使用 一、什么是正则表达式 正则表达式是由普通字符与元字符组成&#xff1a; 普通字符 包括大小写字母、数字、标点符号及一些其他符号。 元字符 是指在正则表达式中具有特殊意义的专用字符&#xff0c;可以用来规定其前导字符…

科技助农 智慧养鸡2D组态可视化管理云平台

前言 养殖业是农业的一个重要分支&#xff0c;主要包括家禽养殖、畜牧养殖和水产养殖三个细分产业。养殖业发展至今&#xff0c;已成为与种植业并列的农业两大支柱产业之一。 建设背景 我国作为养殖业大国&#xff0c;各类型的散养农户、大中小型养殖场数量极多&#xff0c;…

事务的隔离级别有哪几种,解决了什么问题?

事务的隔离级别主要是为了保证多事务环境下的并发安全性&#xff0c;共有四大隔离级别&#xff0c;如下&#xff1a; 如果不考虑隔离性&#xff0c;在SQL操作中&#xff0c;多个事务竞争可能会产生三种不同的现象&#xff0c;分别是脏读、幻读、不可重复读。 首先来看脏读&am…

slf4j log4j log4j-over-slf4j self-log4j12

一、Java日志体系概述 图1-1 Java日志体系概况 日志门面接口&#xff08;SLF4J、JCL&#xff09;一系列绑定和桥接具体的日志实现 先来两个例子&#xff1a; Spring项目中通过Commons Logging直接使用log4j 图1-2 通过Commons Logging使用log4j本例中由于spring-core中原生依…

IS200TREGH1BEC直流并励电动机的启动

​ IS200TREGH1BEC​​​​​​直流并励电动机的启动 要启动机器&#xff0c;电机在零速时产生的扭矩必须超过负载所需的扭矩。然后 TM − TL 将为正&#xff0c;d!/dt 也为正&#xff0c;机器加速。 通常&#xff0c;直流电机的电枢电阻在满载电流时会导致 1% 到 5% 的下降。…

制作RPM包createrepo

每次源码安装很费事&#xff0c;所以直接做一个RPM包 安装 rpm-build 软件包 [rootproxy ~]# yum -y install rpm-build 生成 rpmbuild 目录结构 或许只有在家目录下生成一个不存在的目录才会生成 rpmbuild目录和子目录 若想在自己创建的目录下生成只有自己手动去创建&#…

基于.NET实现的家电维修保养信息系统[含文档+PPT+源码等]精品

1 项目简介 Hi&#xff0c;各位同学好呀&#xff0c;这里是卡莫&#xff01; 基于.NET实现的家电维修保养信息系统[含文档PPT源码等]精品 该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等 软件开发环境及开发工具&#xff1a; 开发软件&#xff1a;…

怎么查看特定的端口是否被屏蔽了

文章目录 怎么查看特定的端口是否被屏蔽了前言无法访问原因查看命令所用环境开启windows的telnet功能查看特定端口是否被墙WSL演示windows终端演示总结 怎么查看特定的端口是否被屏蔽了 前言 如果你不知道自己的服务器的某个端口是否被屏蔽了&#xff0c;通过阅读本文&#x…