多线程数据共享

ThreadLocal详解:解决多线程数据共享的神器 ThreadLocal,顾名思义,就是线程本地变量。它为每个线程提供独立的变量副本,使得每个线程都可以独立地改变自己的副本,而不会影响其他线程。Thread:线程Local:本地的、私有的ThreadLocal是Java多线程编程中的重要工具,它通过为每个线程提供独立的变量副本来解决线程安全问题。ThreadLocal为每个线程提供独立的变量副本使用简单:set()、get()、remove()三个主要方法应用广泛:用户上下文、数据库连接、日期格式化等。 阅读详情

多线程之间的数据共享与可见性

如你所知,任何程序都在一个特殊的内存区域中处理数据。每个进程都有自己的内存,但如果我们在该进程中使用多线程,线程就可以访问这片特定区域,从而彼此进行交互。本节将介绍线程如何通过共享数据进行通信,以及开发者在此过程中可能遇到的问题。


线程之间的数据共享

属于同一进程的线程共享公共内存(称为堆内存)。它们可以通过内存中的共享数据进行通信。为了让多个线程访问同一数据,每个线程都必须持有对该数据的引用(例如一个对象)。下图展示了这一思想:

示意图:多个线程共享堆内存中的对象引用。

让我们来看一个示例。以下是一个名为 Counter 的类:

class Counter {
    var value = 0

    fun increment() {
        value++
    }
}

这个类有一个函数 increment(),每次调用会让 value 字段加 1。

接下来是一个继承自 Thread 的类:

class MyThread(val counter: Counter) : Thread() {
    override fun run() {
        counter.increment()
    }
}

MyThread 构造函数接收一个 Counter 实例,并存储在字段中。run 方法调用 counter.increment()。

现在我们创建一个 Counter 实例和两个 MyThread 实例:

val counter = Counter()

val thread1 = MyThread(counter)
val thread2 = MyThread(counter)

启动这两个线程并打印 counter.value:

thread1.start()  // 启动第一个线程
thread1.join()   // 等待第一个线程完成

thread2.start()  // 启动第二个线程
thread2.join()   // 等待第二个线程完成

println(counter.value) // 输出结果是 2

结果是 2,因为两个线程操作的是同一个 counter 实例,值被增加了两次。

注意:这个例子中两个线程并不是“同时”操作数据的 —— 第二个线程在第一个线程结束后才开始执行。


线程干扰(Thread Interference)

非原子操作是由多个步骤组成的操作。如果一个线程在另一个线程执行的非原子操作中途介入,就可能产生线程干扰问题 —— 多个线程的操作步骤交错执行,产生意料之外的结果。

以 Counter 类为例,value++ 实际上由三个步骤组成:

  1. 读取当前值;

  2. 将值加一;

  3. 将结果写回字段。

由于这个操作是非原子的,若两个线程几乎同时调用 increment(),可能会出现如下情况:

  • 线程 A:读取 value(值为 0)

  • 线程 A:加 1

  • 线程 B:读取 value(值仍为 0)

  • 线程 A:写入结果(值变为 1)

  • 线程 B:加 1

  • 线程 B:写入结果(值仍为 1)

最终结果是 1 而不是 2,线程 A 的结果被线程 B 覆盖了。

类似地,value-- 也存在这个问题。


long 与 double 的原子性问题

你可能会惊讶:即便是读取和写入 long(长整型)或 double(双精度浮点)变量,在某些平台上也不是原子操作。

class MyClass {
    var longVal: Long = 0        // 非原子性
    var doubleVal: Double = 0.0  // 非原子性
}

这意味着一个线程在写入时,另一个线程可能看到的是“写了一半”的中间值(比如只写了前 32 位)。

为了解决这个问题,可以使用 @Volatile 注解:

class MyClass {
    @Volatile var longVal: Long = 0
    @Volatile var doubleVal: Double = 0.0
}

这样就保证了对这些变量的读取与写入是原子的。

注意:Boolean、Byte、Short、Int、Char 和 Float 类型的读取与写入在 JVM 上是原子的,无需加 @Volatile。


线程之间的可见性(Visibility)

在并发程序中,一个线程对共享变量的更改,另一个线程可能看不到这些变化,或者看到的是错乱顺序的值,这叫做可见性问题。

原因在于:

编译器、运行时和 CPU 为了优化性能,可能进行缓存或指令重排序,这些优化会在并发上下文中造成问题。

来看一个示例:

var number = 0
var ready = false

class Reader : Thread() {
    override fun run() {
        while (!ready) {
            yield() // 暂时释放资源
        }
        println(number)
    }
}

fun main() {
    Reader().start()
    number = 42
    ready = true
}

按理说输出应为 42,但实际上也可能输出 0。这是因为:

  • 主线程对 number 和 ready 的修改可能被缓存在寄存器或写缓冲区中;

  • Reader 线程可能读取到旧值(0)或乱序执行后的值。

为了解决这个问题,我们可以使用 @Volatile:

@Volatile var number = 0
@Volatile var ready = false

@Volatile 保证对该字段的更改对所有线程都是立即可见的,并避免了重排序带来的问题。


可见性无需 @Volatile 的两种情况

某些情况下,不使用 @Volatile 也可以保证可见性:

  1. 在线程启动前,对变量的修改对新线程是可见的。

  2. 在 join() 返回之后,该线程内部对变量的修改对其他线程是可见的。


小结

  • 属于同一进程的线程通过堆内存共享数据。

  • 非原子操作的步骤可能交错执行,造成 线程干扰(Thread Interference)。

  • @Volatile 注解保证对变量的修改对所有线程立即可见,并使读取和写入操作具备原子性(不包括递增或递减操作)。

  • @Volatile 不保证复合操作(如 value++)的原子性。

  • 可见性问题在多线程程序中是常见而又隐蔽的 bug 来源。

5G协议栈逆向工程:基于开源模拟器的核心网信令分析实战 本文详细介绍了基于开源模拟器Free5GC和UERANSIM的5G协议栈逆向工程实战,涵盖核心网信令分析、环境搭建、认证协议解析及安全漏洞验证。通过NGAP/NAS信令交互解密和异常流量注入测试,帮助研究人员深入理解5G网络安全机制,并提供实用的安全加固建议。 阅读详情

相关推荐

别再踩坑了!手把手教你用EB Tresos Studio创建Autosar MCAL工程(含模块配置避坑指南)

本文详细介绍了使用EB Tresos Studio创建Autosar MCAL工程的完整流程,包括工程创建前的关键准备、模块配置的避坑指南以及工程验证与代码生成的实用技巧。通过实战案例和黄金准则,帮助开发者高效完成MCAL工程创建,避免常见错误,提升开发效率。

weixin_42536058的博客 445

解密多线程共同操作同一个数据的互斥锁同步技术

互斥锁是一种同步机制,用于保护共享资源在多线程环境下的访问。它通过对资源加锁和解锁的方式,确保同一时间只有一个线程可以访问共享资源,从而避免了竞争条件的发生。互斥锁的基本原理是通过一个标志位来表示资源的可用状态。当一个线程想要访问共享资源时,它首先尝试获取互斥锁,如果互斥锁已经被其他线程持有,则该线程会被阻塞等待,直到互斥锁被释放。一旦线程获取到互斥锁,它就可以安全地访问共享资源,然后在完成后释放互斥锁,以便其他线程可以获取到它。互斥锁同步技术是解决多线程竞争问题的一种常用方法。

博客 579

人工智能顶会投稿指南:CCF-A类会议ACL、ICML、NeurIPS、ICLR的投稿周期与影响力解析

本文详细解析了CCF-A类会议ACL、ICML、NeurIPS、ICLR的投稿周期与影响力,涵盖各会议的投稿时间线、评审特点及录用率。ACL注重自然语言处理,ICML强调机器学习理论,NeurIPS覆盖神经信息处理,ICLR聚焦表征学习。文章还提供了实战投稿策略,帮助研究者提升投稿成功率。

weixin_29211309的博客 3357

java基础2

1、mysql语句执行顺序 from on join where group by having select distinct union order by 我们看到on是在join和where前面的 如果两张表的数据量都比较大的话,那样就会占用很大的内存空间这显然是不合理的。所以,我们在进行表连接查询的时候一般都会使用JOIN xxx ON xxx的语法,ON语句的执行是在JOIN语句之前的,也就是说两张表数据行之间进行匹配的时候,会先判断数据行是否符合ON语句后面的条件,再决定是否JOIN。

trees19990807的博客 1455

OpenClaw实操:给网站装上“智能眼睛”?手把手教你用OpenClaw实时监控网站

本文介绍如何在Windows系统使用OpenClaw构建智能网站监控助手。手把手教你通过配置本地AI模型,让openclaw构建的ai助手自动检测指定网页的内容变化,理解更新重要性,并及时通过邮件、微信、Telegram等渠道发出预警,让你第一时间掌握关键信息,彻底告别手动刷新。

plawuyue的博客 2163

Cadence IC5141保姆级教程:Bandgap电路四大仿真(稳定性/噪声/启动/PSRR)完整流程与调参心得

本文提供Cadence IC5141中Bandgap电路的四大仿真(稳定性仿真、噪声仿真、启动仿真、PSRR仿真)的完整流程与调参心得。从原理图检查到ADE L配置,详细解析各仿真步骤的关键参数设置与优化策略,帮助工程师高效完成电路设计与性能调优。

weixin_30399821的博客 1653

linux------11------共享内存的进程通信

linux------11------共享内存的进程通信

weixin_42714122的博客 404

构建 Skill 的完整指南

Skill开发指南摘要 Skill是用于定制Claude AI行为的指令包,包含SKILL.md主文件及可选脚本、参考文档和资源。本文提供了完整的Skill开发指南: 核心概念 三层渐进式披露结构(元数据/主指令/参考文档) 支持独立工作流和MCP集成 跨平台兼容性(Claude.ai/ClaudeCode/API) 开发流程 规划:明确2-3个具体用例 结构要求:kebab-case命名、必备YAML元数据 编写规范:清晰触发条件、分步指令、错误处理 典型应用模式 • 文档生成(类别1) • 工作流自动化

wjianwei666的专栏 298

C# 多线程如何访问同一个数据

运行下面程序,可以看出thread1中waitOne()只对thread2中的waitOne()起到互斥的作用,但是thread1并不受waitOne()的影响,可以调用多次,只是在调用结束后调用相同次数的ReleaseMutex()就可以。在多线程中,为了使数据保持一致性必须要对数据或是访问数据的函数加锁,在数据库中这是很常见的,但是在程序中大部分都是单线程的程序,所以没有加锁的必要,但是在多线程中,为了保持数据的同步,一定要加锁。将键/值对添加到字典中,或如果Key已存在,更新基于键的现有值的键的值。

m0_74823524的博客 657

霜冰优化算法(RIME)详解:从自然现象到智能优化

霜冰优化算法(RIME)是一种新型元启发式优化算法,灵感源于自然界中霜冰的形成过程。该算法通过模拟软霜和硬霜的不同生长特性,实现了高效的全局探索和局部开发平衡。RIME算法包含三个核心机制:软霜搜索策略模拟随机覆盖特性实现全局探索;硬霜穿刺机制模拟定向生长特性进行局部开发;正向贪婪选择机制确保解的持续优化。与遗传算法、粒子群算法相比,RIME具有参数少、收敛快、自适应平衡等优势,特别适合处理高维复杂优化问题。算法已在工程优化、机器学习等领域获得广泛应用,发表一年内即成为高被引论文。

qq_42212808的博客 786

【2025国赛C题】高教社杯全国大学生数学建模竞赛C题思路+代码+论文

【2025国赛C题】高教社杯全国大学生数学建模竞赛C题思路+代码+论文

订阅付费专栏Matlab(奶茶价版),可赠送奶茶价版付费专栏指定代码1份; 2905

Rust多线程数据共享

1. 怎么创建线程 我们都知道Java中通过实现Runnable接口或继承Thread类,来完成一个线程的创建,那么在Rust中是如何实现一个线程的呢?代码如下。 fn how_to_create_a_thread(){ // 创建一个线程 let thread_handle = thread::spawn(|| { println!("Thread inner"); }); // 阻塞线程,并等待其自己执行完毕 thread_handle.join().u

编号94530 1671

微信小程序 - 详细实现随机生成订单号,微信小程序创建订单号思路,不重复订单号生成的方法,小程序自动生成固定规则的订单号流水号(必须是唯一不重复的订单号,生成唯一订单号)详细生成方法,示例代码教程最新

微信小程序,vue2,vue3,订单号,流水号思路,微信小程序 详细实现随机生成订单号(JavaScript),随机生成商户订单号,根据当前时间随机生成流水号或者订单号,微信小程序之商家订单,订单号随机生成器,自动生成固定规则的订单号(前缀+采购日期+6位流水号)微信小程序 生成外卖顺序号,生成订单号或流水号简单方法,实现订单号生成唯一的解决方案,如何按日期生成订单号,14位编号=8位日期(+6位流水号),自动生成订单号(固定+日期+数字),销售订单号自动生成。在微信小程序开发中,详解实现随机生成创建订单号

🏆 前端领域优质创作者 1188

Java多线程之间的数据共享

转载 上面一篇博客很好地讲述了Java多线程编程地数据共享,在此作为一篇笔记

NP_hard的博客 404

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

多进程和多线程数据共享

多线程之间的数据共享是通过线程共享同一个进程的内存空间来实现的,因此多个线程可以直接访问相同的内存地址,从而实现数据的共享。这使得多线程编程更加方便,因为不需要像多进程那样使用额外的机制进行进程间通信。然而,多线程共享数据也带来了潜在的问题,比如数据竞争和并发访问问题。当多个线程同时访问共享的数据时,可能会导致数据的不一致性或损坏。因此,在进行多线程编程时,需要合理地处理数据共享,避免数据竞争。

www_z_dd的博客 2820

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

​多进程和多线程数据共享​

​多进程和多线程数据共享​

weixin_62713188的博客 1150

java多线程实现数据共享

练习题:卖100个苹果,实现数据共享。新建一个线程有两种方式:1.继承Thread类;2.是实现Runnable的方式:那我们就先使用第一种方式来实现:使用Thread实现数据的共享第一步:写一个了ShareThread继承Thread,并在ShareThread定义一个静态的Apple_Counts变量(注意:Apple_Counts因为是静态的,所以是共享数据) /** * 存在

liujun2son 2651

如何实现多线程之间的数据共享?

浅拷贝是指只复制对象的基本数据类型,对于对象引用的数据,还是引用原来的对象。这是一种更常见的方式,特别是当你需要创建一个对象的子类或包装原始类型时。然后,我们定义了一个构造函数,该函数接受两个参数(一个整数和一个字符串),并将它们赋值给相应的属性。这种方式允许你在不创建实例的情况下直接调用方法,这可能是对封装类的一部分静态数据进行处理的理想方式。深拷贝是指复制对象中的所有元素,包括对象引用的其他对象。在Java中,对象的实例化通常是通过使用类的构造函数来完成的。的其他方法实现深拷贝(Deep Copy)。

WangYaolove1314的博客 1734

多线程编程2. 多线程数据共享

在多个线程中共享数据时,如果多个线程同时访问同一个变量,并且其中至少有一个线程对该变量进行了写操作,那么就会出现数据竞争问题。数据竞争可能会导致程序崩溃、产生未定义的结果,或者得到错误的结果。 常见的同步机制包括**互斥量、条件变量、原子操作**等

zhimin_zeng的博客 941

多线程的数据共享

qq_44719004的博客 195

多线程实现数据不共享与数据共享

数据不共享 要实现多线程数据不共享,让每个线程独立,再调用自己的start( )方法即可。因为Java会为每一个对象创建各自的局部变量和空间,因此要实现数据不共享,只要让每个线程中使用的资源数据不是同一个对象的即可。代码如下(用Runnable接口实现): public class MyRunnable implements Runnable{ private int count=5; ...

weixin_43184215的博客 1526

【并发编程】线程范围内的数据共享,ThreadLocal,多线程间的数据共享

这几天生病了,所以学的比较少,只学习了关于线程中的线程范围数据共享,ThreadLocal以及多线程数据共享。 在这里记下我的理解和代码,其实理解都写在代码注释里了,个人觉得这样更加适合以后复习。 (上一篇我讲的是关于线程的创建,计时器,同步锁,线程间的通信。) 第一个,关于线程范围内的数据共享。 数据共享,一般是多个事件在串在同一个线程里调用同一个数据。下面一个小例子就可以说

IT一刻钟 938

多线程总结(二)数据共享

多线程数据共享,我操作这个数据的时候别人不能干扰到我,否则我读取的数据和操作的数据都是乱的。比如银行取钱,我插入银行卡,发现余额是1000元,于是我准备存200;这时候另一个人(另一个线程)从里面取了200元,我存完之后是1000+200,commit之后是1200吗?这肯定是不对的。 如果多线程执行的代码相同,那么可以使用同一个runnable对象,这个runnable里面的数据共享(买...

lxia_wanshui的博客 208

多线程范围内的数据共享

多线程范围内的共享解决方法参考有4中: 1.如果线程执行的代码相同,多个线程共享同一个runnable对象时,将共享数据放在runnable对象 2.如果多个线程执行的代码不同,将共享数据封装到一个对象中,将这个对象逐一传递给各个runnable对象 3.如果多个线程执行的代码不同,将共享数据作为外部类的final成员变量,将不同的runnable对象作为内部类主动取数据 4.将数据声明为...

weixin_34205826的博客 109

【花雕学编程】Arduino RTOS 之多线程数据共享与同步

通过任务、信号量和互斥锁等机制,RTOS能够有效地管理资源,提高系统的并发处理能力,使其更适合复杂的嵌入式系统开发。在所有案例中,任务的优先级设定为1,FreeRTOS将根据任务的优先级和调度策略来管理任务的执行。5、创新:Arduino可以让你用电子的方式来表达你的创意和想象,你可以用Arduino来制作各种有趣和有用的项目,如机器人、智能家居、艺术装置等。为了避免多个线程同时访问共享资源导致的数据冲突或不一致,RTOS 提供了多种同步机制,如信号量、互斥锁和事件标志等,确保数据的完整性和一致性。

雕爷学编程 1231
上一篇: 自定义线程
下一篇: Kotlin线程同步
Kiri霧
博客等级 码龄3年 432粉丝 156原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值