线程安全的那些事儿

线程安全关乎内存安全,可以通过线程上下文、ThreadLocal、final关键字等方式实现。互斥锁确保同一时刻只有一个线程访问,条件变量用于线程间的协作,而CAS是一种乐观锁,减少不必要的锁操作。本文探讨了这些线程安全策略在Java中的应用。

在网络上看到关于线程安全的问题,在这里,我想结合操作系统中线程的概念,来说说这个问题!

进程是资源分配的最小单位,线程是程序执行的最小单位!也就是说对于进程来说,它的内存空间有一块特殊的公共区域,通常称为堆,这个区域是所有进程内的线程共享的区域。这个区域共享,好处是数据的传输非常方便,坏处是会造成数据的不安全,因为所有线程都能用,这就引出了我们说的线程安全的问题。

那么什么是线程安全?怎么去保证线程安全呢?

线程安全本质上是内存的安全!我们一步一步说明怎么去来实现线程的方法!

每个线程都有自己的线程上下文,包括唯一的整数线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码。既然是这个线程自己的,那么就可以是安全的了,那我们找到了第一个线程安全的地方,将数据放在栈内存中!在操作系统中不难过,这个地方的默认大小是8M。在代码中,我们线程的局部变量一般都是放在栈内存中的,也就是会说局部变量是线程安全的。

上面保证安全的方式是基于位置的,那么对于全局的变量,也就是常说的类的成员变量,可不可以保证安全呢?答案是可以的!我们给每个线程都拷贝一份呗,这样线程只处理自己的这一份拷贝,就可以了。在Java中,我们的ThreadLocal类就是这样实现的。这个属于数据上的隔离了。

还有没有别的方法?当然是有的,我们经常会遇到一种数据,只读数据!这名字一听就是线程安全的啊,“只可远观而不可亵玩焉”!在Java中,我们通过final关键字定义这样的数据。

那么除了上面这些数据,我们还有哪些保证线程安全的方式。

要想保证线程安全,我们同一时刻只让一个线程去访问,不就行了吗?这个时候我们引入了互斥锁!谁先进入这个内存区域读取数据,我们门口放一把锁,进入房间我们就把房门锁住,钥匙在这个线程手里,不用了就打开锁,把钥匙送还。这就是互斥锁实现的机制。

当然,这个也是有弊端的,网上有个非常好的解释,为了解决这个问题,就引入了条件变量的概念。下面我贴出网上关于互斥锁和条件变量的恩怨情仇!

两个线程操作同一临界区时,通过互斥锁保护,若A线程已经加锁,B线程再加锁时候会被阻塞,直到A释放锁,B再获得锁运行,进程B必须不停的主动获得锁、检查条件、释放锁、再获得锁、再检查、再释放,一直到满足运行的条件的时候才可以(而此过程中其他线程一直在等待该线程的结束),这种方式是比较消耗系统的资源的。而条件变量同样是阻塞,还需要通知才能唤醒,线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,该线程就休眠了,应该仍阻塞在这里,等待条件满足后被唤醒,节省了线程不断运行浪费的资源。这个过程一般用while语句实现。当线程B发现被锁定的变量不满足条件时会自动的释放锁并把自身置于等待状态,让出CPU的控制权给其它线程。其它线程 此时就有机会去进行操作,当修改完成后再通知那些由于条件不满足而陷入等待状态的线程。这是一种通知模型的同步方式,大大的节省了CPU的计算资源,减少了线程之间的竞争,而且提高了线程之间的系统工作的效率。这种同步方式就是条件变量。

而在Java中,其实就是线程之间的互斥和协作,条件变量就是用来协作的,对应java里的wait()和notify()函数。

上面这个样子能够保证线程安全,但是,锁的获取和释放是需要消耗资源的,如果线程的数量特别少的时候,可能根本没有别的线程来操作我们的数据,这个时候还这样就浪费了啊。

于是,我们引入了一种机制--CAS(compare and swap)。它其实也是一种锁,只是没有那么麻烦,属于乐观锁。所谓乐观锁就是秉持乐观向上的态度,我的数据是不会被意外修改的,修改了,就从头再来呗!

CAS翻译过来就是比较替换,它有3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

好了,我们的线程安全就说完了,其实这篇文章是基于Java来说的,由于本人太久没有弄Java的东西,忘掉了很多东西,本文也没有实例,但是对于线程安全的理解应该有一定的帮助,挖个坑,希望后面能补!

最后总结一下,就是以下这些了!

前两种属于隔离法,一个是位置隔离,一个是数据隔离。
然后两种是标记法,一个是只读标记,一个是加锁标记。
最后一种是大胆法,先来怼一把试试,若不行从头再来。

参考资料

https://zhuanlan.zhihu.com/p/67905621

 

内容概要:本文系统介绍了算术优化算法(AOA)的基本原理、核心思想及Python实现方法,并通过图像分割的实际案例展示了其应用价值。AOA是一种基于种群的元启发式算法,其核心思想来源于四则运算,利用乘除运算进行全局勘探,加减运算进行局部开发,通过数学优化器加速函数(MOA)和数学优化概率(MOP)动态控制搜索过程,在全局探索与局部开发之间实现平衡。文章详细解析了算法的初始化、勘探与开发阶段的更新策略,并提供了完整的Python代码实现,结合Rastrigin函数进行测试验证。进一步地,以Flask框架搭建前后端分离系统,将AOA应用于图像分割任务,展示了其在实际工程中的可行性与高效性。最后,通过收敛速度、寻优精度等指标评估算法性能,并提出自适应参数调整、模型优化和并行计算等改进策略。; 适合人群:具备一定Python编程基础和优化算法基础知识的高校学生、科研人员及工程技术人员,尤其适合从事人工智能、图像处理、智能优化等领域的从业者;; 使用场景及目标:①理解元启发式算法的设计思想与实现机制;②掌握AOA在函数优化、图像分割等实际问题中的建模与求解方法;③学习如何将优化算法集成到Web系统中实现工程化应用;④为算法性能评估与改进提供实践参考; 阅读建议:建议读者结合代码逐行调试,深入理解算法流程中MOA与MOP的作用机制,尝试在不同测试函数上运行算法以观察性能差异,并可进一步扩展图像分割模块,引入更复杂的预处理或后处理技术以提升分割效果。
### QImage 类在多线程环境中的安全性 QImage 对象本身不是完全线程安全的。然而,在特定情况下可以安全地在线程间操作 QImage 实例: - 当 `QImage` 的实例被传递给其他线程时,如果该 `QImage` 不再由原始线程访问,则可以在新线程中安全使用[^1]。 对于图像处理任务而言,通常的做法是在后台线程执行耗时的操作(如颜色反转),而主线程继续响应用户界面事件。当后台线程完成其工作后,可以通过适当的方式通知主线程更新 UI。 为了确保线程安全性和高效性,建议遵循以下实践方法之一: #### 方法一:利用 QtConcurrent::run 进行异步调用 通过 `QtConcurrent::run()` 可以方便地在一个独立的工作线程中运行指定函数,从而避免阻塞 GUI 主循环。此方式适用于简单的、不需要复杂控制流的任务。 ```cpp // 创建 QImage 对象 QImage image = ...; // 使用 QtConcurrent::run 将 invertPixels 操作放到单独线程执行 QFuture<void> future = QtConcurrent::run(&image, &QImage::invertPixels, QImage::InvertRgba); // 继续做其它事情... future.waitForFinished(); // 等待直到像素翻转完毕 ``` 这种方法简单易用,并且自动管理了底层线程资源,减少了手动同步带来的风险。 #### 方法二:采用信号槽机制配合 QThread 或者 QThreadPool 更复杂的场景下,可能需要自定义子类继承 `QThread` 来实现更加精细的并发逻辑;或者利用 `QThreadPool` 结合 `QRunnable` 提供更好的性能和灵活性。这两种方案都允许开发者更好地掌控各个阶段的状态变化以及数据交换过程。 需要注意的是,无论选择哪种途径实施多线程编程模型,都应该谨慎对待共享变量及其互斥访问问题,必要时引入锁机制保护临界区代码片段以防竞态条件发生。 综上所述,虽然 `QImage` 并不具备内在意义上的线程安全性,但是借助于上述提到的技术手段仍然能够有效地将其应用于高效的并行计算环境中去[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值