【多线程 7】大厂面试必问高频问题之——线程安全问题

🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇

                                     多线程安全 

🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇

今日推荐歌曲:  lf December Never Ends   -- Anson Seabra   🎵🎵


系列文章目录

【多线程1】多线程之面试常考题——进程线程联系和区别(全面详解)-CSDN博客

【多线程系列 2】线程的多种状态 ! ! !-CSDN博客

【多线程系列 4】机制的“神锁“———synchronized-CSDN博客

【多线程 5】 HashTable, HashMap, ConcurrentHashMap 三者之间的区别-CSDN博客

【多线程 6】锁策略, cas 和 synchronized 优化过程-CSDN博客


文章目录

前言

一、为什么存在线程安全问题?

修改共享数据

原⼦性

内存可见性

指令重排序

其他安全问题

二、如何解决线程安全问题

总结


前言

这篇文章会给大家详细介绍有关线程安全问题和解决方案。


一、为什么存在线程安全问题?

线程调度是随机的 这是线程安全问题的 罪魁祸⾸

随机调度使⼀个程序在多线程环境下,执⾏顺序存在很多的变数. 程序猿必须保证在任意执⾏顺序下,代码都能正常⼯作.

修改共享数据

多 个线程修改同⼀个变量

共享资源:多个线程同时访问和修改同一块共享资源,如全局变量、静态变量、堆内存、文件等,而没有适当的同步机制来保护共享资源的访问。


原⼦性

非原子操作:对共享资源的操作不是原子的,即不能保证在单个线程执行过程中不被中断,可能导致数据状态的不一致性。

竞态条件:多个线程之间存在对共享资源的竞争,导致执行结果依赖于线程执行的顺序或时间,从而产生不确定性或错误结果。

⼀条java语句不⼀定是原⼦的,也不⼀定只是⼀条指令

什么是原⼦性?

我们把⼀段代码想象成⼀个房间,每个线程就是要进⼊这个房间的⼈。如果没有任何机制保证,A进⼊ 房间之后,还没有出来;B是不是也可以进⼊房间,打断A在房间⾥的隐私。这个就是不具备原⼦性的。 那我们应该如何解决这个问题呢?是不是只要给房间加⼀把锁,A进去就把⻔锁上,其他⼈是不是就进 不来了。这样就保证了这段代码的原⼦性了。

有时也把这个现象叫做同步互斥,表⽰操作是互相排斥的。

我们平时用到的n++,其实是由三步操作组成的:

1. 从内存把数据读到CPU

2. 进⾏数据更新

3. 把数据写回到CPU

不保证原⼦性会给多线程带来什么问题

如果⼀个线程正在对⼀个变量操作,中途其他线程插⼊进来了,如果这个操作被打断了,结果就可能 是错误的。如下图:


内存可见性

数据竞争:多个线程同时对共享数据进行读取和写入操作,而没有适当的同步机制来保证数据的一致性和可见性,可能导致数据损坏或不一致。      

可⻅性指,⼀个线程对共享变量值的修改,能够及时地被其他线程看到.   

Java内存模型(JMM):

Java虚拟机规范中定义了Java内存模型. ⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的 并发效果

  •  线程之间的共享变量存在主内存(MainMemory).
  • 每⼀个线程都有⾃⼰的"⼯作内存"(WorkingMemory).
  • 当线程要读取⼀个共享变量的时候,会先把变量从主内存拷⻉到⼯作内存,再从⼯作内存读取数据.
  • 当线程要修改⼀个共享变量的时候,也会先修改⼯作内存中的副本,再同步回主内存.

由于每个线程有⾃⼰的⼯作内存,这些⼯作内存中的内容相当于同⼀个共享变量的"副本".此时修改线 程1的⼯作内存中的值,线程2的⼯作内存不⼀定会及时变化.

这里可以使用 volatile 关键字保证内存可⻅性


指令重排序

执行顺序问题:多线程程序中,线程的执行顺序可能受到操作系统调度器、编译器优化等因素的影响,导致程序的执行结果与预期不符。

指令重排序是现代处理器为了提高性能而采取的一种优化技术。它指的是处理器在执行指令时可能会重新排列指令的执行顺序,以最大程度地利用处理器资源、减少流水线中的空闲周期和提高指令级并行度。然而,这种优化可能导致多线程程序出现意外行为,因为在多线程环境下,指令的执行顺序可能会影响程序的正确性。

指令重排序主要分为三种类型:

  1. 编译器重排序(Compiler Reordering):编译器在生成目标代码时会根据编译器优化策略重排指令顺序,以提高程序性能。例如,可以将无关的指令重新排序或将多个语句合并为一个复合语句。

  2. 处理器重排序(Processor Reordering):现代处理器具有多级流水线、乱序执行和分支预测等特性,这使得处理器可以在执行时对指令进行重排序,以提高指令级并行度和性能。

  3. 内存系统重排序(Memory System Reordering):处理器与内存之间存在多级缓存、缓存一致性协议等机制,这可能导致读写内存操作的顺序发生重排序,从而影响多线程程序的内存可见性。

指令重排序可能会导致多线程程序出现以下问题:

  • 数据竞争(Data Race):如果多个线程对共享变量进行读写操作,并且这些操作发生在不同的指令重排序序列中,可能会导致数据的不一致性。

  • 程序逻辑错误(Program Logic Errors):程序的正确性可能依赖于特定的指令执行顺序,如果指令重排序改变了原本的执行顺序,可能会导致程序逻辑错误。

为了避免指令重排序带来的问题,可以使用同步机制来确保指令的执行顺序,如使用内存屏障、volatile关键字、互斥锁、读写锁等。另外,一些编程语言和框架也提供了特定的工具和接口来帮助开发者处理指令重排序问题。


其他安全问题

缓存一致性:现代计算机系统中,多核处理器和多级缓存会引入缓存一致性问题,即不同线程对同一块内存区域的修改可能会在各自的缓存中产生不一致的副本,从而导致线程间通信错误。

信号处理和中断:在信号处理和中断处理过程中,也可能存在对共享资源的访问,需要特别注意同步和互斥的问题。

隐式共享:某些情况下,共享资源的访问可能是隐式的,例如函数调用、对象的拷贝等,容易忽略对共享资源的同步保护。

资源管理问题:在多线程程序中,可能存在资源管理不当的问题,如内存泄漏、死锁等,也可能导致线程安全问题的发生。

综上所述,处理线程安全问题需要综合考虑多个方面的因素,并采取适当的同步机制和并发控制策略来保证共享资源的安全访问和一致性。


二、如何解决线程安全问题

为了解决线程安全问题,可以采取以下几种解决方案:

  1. 锁机制:使用锁(如互斥锁、读写锁)来保护共享资源的访问,确保在任何时候只有一个线程可以修改共享资源,从而避免竞态条件和数据竞争。

  2. 原子操作:利用原子操作(Atomic Operations)来保证对共享数据的操作是不可分割的,从而避免数据竞争和并发访问导致的问题。

  3. 同步工具:使用同步工具(如信号量、条件变量、屏障等)来协调多个线程的执行顺序,确保线程之间的同步和协作。

  4. 不可变对象:设计不可变对象(Immutable Objects),使得对象的状态不可改变,从而避免并发修改导致的线程安全问题。

  5. 并发数据结构:使用专门设计的并发数据结构(如并发队列、并发哈希表等)来处理并发访问,确保数据结构的线程安全性。

  6. 读写锁(Read-Write Lock):针对读多写少的场景,可以使用读写锁来提高并发性能。读写锁允许多个线程同时读取共享资源,但只有一个线程可以写入共享资源,从而减少了写操作的竞争。

  7. 内存模型(Memory Model):了解并遵循编程语言和平台的内存模型规范,确保对共享数据的访问在不同线程之间具有一致的可见性和顺序性。

  8. 并发编程框架:利用现代并发编程框架(如Java中的java.util.concurrent包、C++中的std::thread库、Python中的concurrent.futures模块等)提供的高级并发工具和数据结构,简化并发编程并减少线程安全问题的发生。


总结

处理线程安全问题需要综合考虑并发控制策略、同步机制、性能优化和编程规范等多个方面,以确保多线程程序的正确性、性能和可维护性。

以上就是今天的内容,谢谢观看.

  • 30
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值