线程安全问题及解决

1.前言

当我们使用多个线程访问同一资源时(可以是同一变量,同一文件,同一条记录),若多个线程只要只读操作,则不会发生线程安全问题;如果多个线程既有可读又有可写操作时,将可能导致线程安全问题.

2.提出问题

例 : 三个人对银行账户存储的100块存款进行取钱,如果该账户还有存款,就可以取.该问题可能发生线程安全问题吗?

3.继承Thread类的方式进行模拟 : 

public class ThreadTest {public static void main(String[] args) {MulterThread t1 = new MulterThread("线程-1");MulterThread t2 = new MulterThread("线程-2");MulterThread t3 = new MulterThread("线程-3");t1.start();t2.start();t3.start();}
}
class MulterThread extends Thread {static int change = 100;public MulterThread() {super();}public MulterThread(String name) {super(name);}@Overridepublic void run() {while(true) {if (change > 0) {System.out.println(Thread.currentThread().getName() + "\t\t" + change);change--;} else {break;}}}
}控制台 : 
//显然有问题,100的时候被取的两次
线程-2		100
线程-1		100
线程-2		99
线程-1		98
线程-2		97
线程-1		96
线程-2		95
线程-1		94
线程-1		92
线程-2		93
略

注 : 

  • 为什么change变量要声明为static : 如果不声明为static,那么new了三个MulterThread对象,就会有300块的存款,与抢占同一资源的场景不符.
  • 为什么会出现两次100呢 : 很显然,每次运行结果不一样,按该次运行结果举例.当线程2调用run()方法进入输出语句的时候,执行到下一句change--还需要一段时间,而此时线程1也调用了run(),并也执行到了输出语句,此时change--语句并未执行,所以二者都打印的是100.

3.实现Runnable接口的方法进行模拟

public class RunnableTest {public static void main(String[] args) {A a = new A();Thread t1 = new Thread(a);Thread t2 = new Thread(a);Thread t3 = new Thread(a);t1.start();t2.start();t3.start();}}
class A implements Runnable{int change = 100;@Overridepublic void run() {while (true) {if (change > 0) {System.out.println(Thread.currentThread().getName() + "\t\t" + change);change--;} else {break;}}}
}控制台 : 
Thread-1		100
Thread-1		99
Thread-1		98
Thread-0		100
Thread-1		97
Thread-0		96
Thread-2		100
Thread-0		94
Thread-1		95
Thread-1		91
略

注 : 

  • 为什么change变量不用static修饰 : 只调用一次new创建了A的一个对象,并作为同一个实参传入到Thread类中.因为只new了一次,所以change只有一份.
  • 为什么会出现三次100 : 与上同.

4.解决方案

必须满足一个线程在操作change时,其他线程必须等待,直到该线程操作完成后,其他线程才可以进来操作change.

5.方式1 : 同步代码块

(1). 格式

synchronized(同步监视器){

    //需要被同步的代码

}

(2). 利用锁来解决继承Thread类带来的线程安全问题.

@Overridepublic void run() {while(true) {synchronized (MulterThread.class){if (change > 0) {System.out.println(Thread.currentThread().getName() + "\t\t" + change);change--;} else {break;}}}}

(3). 利用锁来解决实现接口带来的线程安全问题 : 

@Overridepublic void run() {while (true) {synchronized (this){if (change > 0) {System.out.println(Thread.currentThread().getName() + "\t\t" + change);change--;} else {break;}}}}

说明 : 

  • 需要被同步的代码,即为操作共享数据的代码.
  • 共享数据 : 即多个线程可以操作的数据 : 如该处的change.
  • 需要被同步的代码,在被synchronized包裹后,就使得一个线程操作共享数据时,其他线程需等待.
  • 同步监视器(锁) : 哪个线程获得了锁,哪个线程就可以执行被同步的代码.
  • 锁可以由任何对象充当,但必须多个线程共用同一个同步监视器.(即该监视器必须唯一).
  • 继承Thread类 : 锁---->类名.class
  • 实现接口 : 锁------>this

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

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

相关文章

【踩坑】使用CenterNet训练自己的数据时的环境配置与踩坑

环境配置 系统:Ubuntu22.04 Python:3.8 CUDA:11.7 pytorch:2.2.1 因为种种原因没有使用原工程的老版本python和pytorch……但总之也跑通了,可喜可贺,可喜可贺。 新建conda环境: conda create -…

JVM–内存模型/垃圾回收流程

JVM(Java 虚拟机)的内存模型和垃圾回收流程是 Java 程序运行时的重要组成部分,下面是关于 JVM 内存模型和垃圾回收流程的简要介绍: JVM 内存模型: 堆内存(Heap):用于存储对象实例和数…

2024java面试题

题目:反转一个单链表描述:给定一个单链表的头节点,将其反转,返回反转后的链表的头节点。 题目:合并两个有序链表描述:给定两个有序链表的头节点,将它们合并成一个有序链表,返回合并…

JavaEE之网络初识(网络中的一些基本概念)详解

😽博主CSDN主页: 小源_😽 🖋️个人专栏: JavaEE 😀努力追逐大佬们的步伐~ 目录 1. 前言 2. 网络中的一些基本概念 2.1 IP地址 2.2 端口号 2.3 网络协议 2.4 协议分层 2.5 封装 2.6 分用 (封装的逆向过程) 2.7 客户端 vs …

asyncio和 aiohttp

文章目录 asyncio和 aiohttp3.8版本 特性aiohttp案例优化方案 asyncio和 aiohttp asyncio即Asynchronous I/O是python一个用来处理并发(concurrent)事件的包,是很多python异步架构的基础,多用于处理高并发网络请求方面的问题。 为了简化并更好地标识异…

JUC并发编程之常用方法

sleep() public void testSleepAndYield() {Thread t1 new Thread(() -> {try {log.debug("t1-sleep...");Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}, "t1");log.debug("t1 start 前的状态&#…

数学建模智能算法

模拟退火算法 %生成初始解,求目标函数f(x)x1^2x2^28在x1^2-x2>0;-x1-x2^220约束下的最小值问题 sol_new21;%(1)解空间(初始解) sol_new12-sol_new2^2; sol_current1 sol_new1; sol_best1 sol_new1; so…

Appium设备交互API

设备交互API指的是操作设备系统中的一些固有功能,而非被测程序的功能,例如模拟来电,模拟发送短信,设置网络,切换横竖屏,APP操作,打开通知栏,录屏等。 模拟来电 make_gsm_call(phon…

Douyin视频详情数据API接口(视频详情,评论)

抖音官方并没有直接提供公开的视频详情数据采集API接口给普通用户或第三方开发者。抖音的数据采集通常受到严格的限制,以保护用户隐私和平台安全。 请求示例,API接口接入Anzexi58 如果您需要获取抖音视频详情数据,包括评论、点赞等&#xff…

软考 - 系统架构设计师 - 关系模型的完整性规则

前言 关系模型的完整性规则是一组用于确保关系数据库中数据的完整性和一致性的规则。这些规则定义了在关系数据库中如何存储、更新和查询数据,以保证数据的准确性和一致性。 详情 关系模型的完整性规则主要包括以下三类: 实体完整性规则 这是确保每个…

C++基本语法

C是如何工作的 文章目录 C是如何工作的1、新建Hello World工程1.1使用Visual Studio新建项目1.2 HelloWorld1.2.1 命名空间1.2.2 输出输出 1.3 注释1.4 函数1.4.1 使用有返回的函数1.4.2 自定义函数 1、新建Hello World工程 1.1使用Visual Studio新建项目 按照下面的图片&…

Linux 性能分析工具 perf 的使用指南

什么是perf,可以用来干什么 perf 是 Linux 内核的性能分析工具集,它可以用来监控和分析系统和应用程序的性能。perf 提供了一系列功能强大的子命令,可以帮助开发者和系统管理员: 监控 CPU 使用率:识别最消耗 CPU 的代…

x86的内存分段机制

8086 是 Intel 公司第一款 16 位处理器,诞生于 1978 年,所以说它很古老。 一.8086 的通用寄存器 8086 处理器内部共有 8 个 16 位的通用处理器,分别被命名为 AX、 BX、 CX、 DX、 SI、 DI、 BP、 SP。如下图所示。 “通用”的意思是…

利用python搭建临时文件传输服务

场景 如果想从一台服务器上传输文件又多种方法,其中常见的是利用scp进行传输,但是需要知道服务器的账号密码才能进行传输,但有时候我们并不知道账号密码,这个时候我们就可以通过python -m SimpleHTTPServer 命令进行传输文件 启…

C语言例4-14:从键盘输入小写字母转换成大写字母并输出。

代码如下&#xff1a; //从键盘输入小写字母转换成大写字母并输出。 #include<stdio.h> int main(void) {char c1,c2;printf("输入小写字母&#xff1a; \n");c1 getchar(); //从键盘输入一个字符putchar(c1);printf(",%d\n",c1);c2 c1-32; …

Git基础(23):Git分支合并实战保姆式流程

文章目录 前言准备正常分支合并1. 创建两个不冲突分支2. 将dev合并到test 冲突分支合并1. 制造分支冲突2. 冲突合并 前言 Git分支合并操作 准备 这里先在Gitee创建了一个空仓库&#xff0c;方便远程查看内容。 正常分支合并 1. 创建两个不冲突分支 &#xff08;1&#xf…

C++ 控制语句(二)

一 break continue和goto语句 1 break语句 在switch语句中&#xff0c;分隔case子句&#xff0c;跳出switch语句。 在循环语句中可以立即终止循环语句的执行。 2 continue语句 功能:在一次循环过程中,跳过continue语句以下的语句,直 接进入下一次循环操作。 3 goto语句 …

Poetry是一个现代的Python包管理工具

Poetry是一个现代的Python包管理工具&#xff0c;它旨在简化包的声明、管理和发布过程。Poetry解决了Python项目中的一些常见问题&#xff0c;如依赖管理、包版本控制以及项目的打包和发布。它被设计为一站式的解决方案&#xff0c;提供了一系列的特性来处理Python包的生命周期…

Linux文件系统和日志管理

文件系统的组成 Linux 文件系统会为每个文件分配两个数据结构&#xff1a;索引节点&#xff08;index node&#xff09; 和 目录项&#xff08;directory entry&#xff09;&#xff0c;它们主要用来记录文件的元信息和目录层次结构。 索引节点&#xff0c;也就是 inode&#…

Rustdesk客户端编译后固定密码不稳定时好时坏

环境&#xff1a; rustdesk1.19 问题描述&#xff1a; Rustdesk客户端编译后固定密码不稳定时好时坏 解决方案&#xff1a; 出现固定密码不稳定的问题可能有多种原因&#xff0c;下面是一些可能的解决方法&#xff1a; 密码强度&#xff1a;确保所设置的固定密码足够强大…