【Java多线程编程】解决线程的不安全问题之synchronized关键字

news2024/12/1 0:43:39

前言: 

当我们进行多线程编程时候,多个线程抢占系统资源就会造成程序运行后达不到想要的需求。我们可以通过 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 关键字来对线程中的某个代码块进行加锁。这样就能保证程序正在正常的运行,也就解决了线程的不安全问题。当然,关于线程不安全的处理还有另一种方式那就是使用 volatile关键字 ,大家可以在下方专栏中查阅。 

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

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

🗂️JavaSE的学习:JavaSE

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

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

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

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

相关文章

PVE安装高大全固件

PVE安装高大全的时候&#xff0c;把IMG挂在光驱上&#xff0c;发现无法运行。 后来明白了&#xff0c;openwrt的镜像直接就是个系统磁盘镜像&#xff0c; 没有引导启动项和安装程序&#xff0c;度娘和CSDN后才知道。 ———— 创建个虚拟机&#xff0c;按照引导创建虚拟机即…

女网红靠GPT-4交1000+男友,聊天按分钟收费,一周收入50万

来源 | 量子位 作者 | 鱼羊 注意看&#xff0c;这个女人叫卡琳&#xff0c;靠着GPT-4&#xff0c;她现在同时谈着1000男朋友。 对&#xff0c;我知道事情听上去有些离谱。就连GPT-4自己&#xff0c;都直呼“我一个AI都觉得非常不常见”。 但是先别急&#xff0c;因为更让人挠头…

一个用于Allen脑图谱基因数据的工具箱|abagen详细使用教程-获取基于脑区的基因表达矩阵(脑区*gene)

艾伦人类脑图谱&#xff08;Allen Human Brain Atlas&#xff09; 艾伦人类脑图谱是一个由艾伦脑科学研究所(Allen Institute for Brain Science)开发的在线基因表达图谱数据库&#xff0c;旨在提供人类大脑各个区域的细胞类型和基因表达信息。这个数据库包含了人类全基因组微…

工业识别与定位系统源码解决方案

工厂人员定位系统源码&#xff0c;工业领域定位系统源码 近年来人员定位系统在工业领域的发展势头迅猛&#xff0c;工业识别与定位成为促进制造业数字化的关键技术。通过实时定位可以判断所有的人、物、车的位置。实时定位系统要适用于复杂工业环境&#xff0c;单一技术是很难…

tinyWebServer 学习笔记——二、HTTP 连接处理

文章目录 一、基础知识1. epoll2. 再谈 I/O 复用3. 触发模式和 EPOLLONESHOT4. HTTP 报文5. HTTP 状态码6. 有限状态机7. 主从状态机8. HTTP_CODE9. HTTP 处理流程 二、代码解析1. HTTP 类2. 读取客户数据2. epoll 事件相关3. 接收 HTTP 请求4. HTTP 报文解析5. HTTP 请求响应 …

OpenText 数据迁移解决方案的工作原理及其优势

OpenText Migrate 让迁移变得简单 选择正确的迁移技术您所需要了解的事情 无痛迁移 当谈到停机的常见原因时&#xff0c;灾难往往会得到最多的关注。 但灾难只是导致停机的一小部分原因。 计划的停机时间造成了资源的真正消耗&#xff0c;许多纯粹为灾难恢复应运而生的工具和…

【Simulink】 0基础入门教程 P2 常用模块的使用介绍

目录 常用模块介绍 (1) relational operator&#xff0c;用于数值的大小比较 (2) compare to constant&#xff0c;用于和数值做大小比较 (3)logical operator&#xff0c;用于逻辑运算 与运算 或运算 非运算 (4) switch&#xff0c;类似于C语言中的if 语句&#xff0c…

stm32 MCU液晶TM1622 HT1622驱动调试

本文使用的例程软件工程代码如下 (1条消息) stm32MCU液晶TM1622HT1622驱动调试&#xff0c;源代码&#xff0c;实际项目使用资源-CSDN文库 HT1622/HT1622G/TM1622是一款常用的LCD驱动芯片 TM1622/HT1622厂家不一样&#xff0c;但是芯片功能基本上一直&#xff0c;硬件上基本…

『C++』C++的类型转换

「前言」文章是关于C特殊类型转换 「归属专栏」C嘎嘎 「笔者」枫叶先生(fy) 「座右铭」前行路上修真我 「枫叶先生有点文青病」 「每篇一句」 有些事不是看到了希望才去坚持&#xff0c; 而是因为坚持才会看到希望。 ——《十宗罪》 目录 一、C语言中的类型转换 二、为什么C需…

tinyWebServer 学习笔记——三、定时器处理非活跃链接

文章目录 一、基础知识1. 概念2. API3. 信号处理机制 二、代码解析1. 信号处理函数2. 信号通知逻辑3. 定时器4. 定时器容器5. 定时任务处理函数6. 使用定时器 参考文献 一、基础知识 1. 概念 非活跃&#xff1a;指客户端与服务器建立连接后&#xff0c;长时间不交换数据&…

第二章 数据的表示和运算

1.进位计数制 其他进制转十进制 二进制<——> 八进制&#xff0c;十六进制 (注意&#xff1a;小数部分也是从右往左算十进制——>任意进制&#xff08;整数部分&#xff09; 十进制——>任意进制&#xff08;小数部分&#xff09; 十进制转二进制&#xff08;拼凑…

【gitee流水线实现自动化部署】

首先进入自己的gitee仓库 创建流水线 配置基本信息 名称标识 事件监听 -----触发条件 主要是任务排编内 vue前端则选择node构建 这些就是字面意思 若无特殊需求 按照默认的即可 构建完之后添加新任务 主机部署 选择部署 主机部署 添加主机组 新建主机组 自主导入 之后配…

配置Git

1.安装Git git官网 2.配置Git 在点击桌面上的Git Bash快捷图标中输入&#xff1a; 配置用户名&#xff1a; git config --global user.name "username" //&#xff08; "username"是自己的账户名&#xff0c;&#xff09; 配置邮箱&#xff1a; git…

Mac终端主题配置

如果你不想安装item2这类第三方终端&#xff0c;可以试试我下面的步骤&#xff0c;先上效果图&#xff0c;如果感觉还符合你的胃口&#xff0c;可以继续读下去啦!!! 1.下载item2的主题安装包 https://github.com/mbadolato/iTerm2-Color-Schemes 2.解压缩&#xff0c;打开…

电脑断电文件丢失如何找回?给你支几招!

电脑断电文件丢失如何找回&#xff1f;我好不容易熬夜加班做的活动方案&#xff0c;正当将U盘文件转移到笔记本电脑的时候&#xff0c;没有注意笔记本的电量&#xff0c;在转移数据的过程中突然断电了。我的电脑一下子就“熄”了&#xff0c;方案都没来得及保存。这真是一个悲剧…

06. git关联远程仓库

大家好&#xff0c;前面几节&#xff0c;我们用很长的篇幅介绍了git本地使用过程中的一些基本命令&#xff0c;本节开始&#xff0c;我们介绍通过远程仓库多人协作的时候&#xff0c;基本操作以及遇见的问题。 本节内容预告&#xff1a; 1、github 与gitlab简介 2、git本地连接…

【软考高项】项目范围管理中的需求跟踪矩阵说明

文章目录 需求跟踪矩阵的创建角色步骤 需求跟踪矩阵变更角色步骤 需求跟踪矩阵是把产品需求从其来源连接到能满足需求的可交付成果的一种表格。使用需求跟踪矩阵&#xff0c;把每个需求与业务目标或项目目标联系起来&#xff0c;有助于确保每个需求都具有业务价值。 需求跟踪矩…

从零开始Vue3+Element Plus的后台管理系统(三)——按需自动引入组件和unplugin-vue-components

按需导入Element Plus遇到页面卡顿问题 本项目使用Element Plus的方式是按需自动导入 首先安装unplugin-vue-components 和 unplugin-auto-import这两款插件 npm install -D unplugin-vue-components unplugin-auto-import然后把下列代码插入到你的 Vite 配置文件中 Vite# …

Salesforce Experience Cloud_体验云顾问认证考试-备考攻略 (内含模拟练习题)

Salesforce Experience Cloud顾问认证专为具有Experiences应用程序实施和咨询经验的顾问设计的&#xff0c;适用于使用Experience平台的声明性自定义功能展示其在设计、配置、构建和实施Salesforce Experience应用程序方面的技能和知识的备考者。 备考者需要有6个月的Experien…

周赛345(模拟、分类讨论、DFS求完全联通分量)

文章目录 周赛345[2682. 找出转圈游戏输家](https://leetcode.cn/problems/find-the-losers-of-the-circular-game/)模拟 [2683. 相邻值的按位异或](https://leetcode.cn/problems/neighboring-bitwise-xor/)方法一&#xff1a;分类讨论&#xff08;反向思考&#xff09;方法二…