【线程安全】线程安全的注意事项
有哪些场景需要额外注意线程安全
访问共享变量或资源#
当在多线程的环境下,多个线程去访问共享的缓存或者对象时,同时操作会对结果造成不用程度的改变,尤其是在操作上不具备原子性的操作上时会发生这种情况,例如我们之前在【线程安全】 三类线程安全问题章节说到的i + +问题,他其实就属于一种对共享变量访问时,由于i + +不是原子性操作,导致结果不是预期的结果。
依赖时序的操作#
在回到i + +的问题上,i + +在cup执行的时候,其实也是一种线程安全的问题。
我们再看到这张图,两个线程都同时去执行i + +操作,由于i + +不是原子性操作,这样就会造成第一次i + +还没完成就被第二个线程拿去i + +这样就会操作两次执行的结果一致,但结果就不对了。其实正确的时序性的操作应该是线程1的i + +操作完成之后再去执行i + +的操作。
说到底,如何保证时序的操作,你就保证他这个操作的原子性就可以了,只要让他再执行这段代码的逻辑时,不会被其他的线程抢去执行。
不同数据之间存在绑定关系#
这种常发生在业务代码中,比如有两个线程,一个线程获取学生的名字,一个线程获取线程的学号。如果学生的学号变了,学生的名字也会跟着变化,那么这个时候就会容易出现其中一个线程更新不及时,导致学生的名字和学号对不上。导致数据有误。那在这种情况下,我们也是需要去保持线程的原子性。
没有申明自己是线程安全的方法#
这种其实在很多新手开发会发生的错误,在使用多线程时,如果使用ArrayList,多个线程同时对它进行数据操作,那么这个时候就会出现数据错误,这个原因就是因为它不是线程安全的,如果需要在多线程的环境下去操作List集合,就需要去使用CopyOnWriteArrayList。
所以在我们日常写多线程的时候,如果用到公共的方法,尤其是在会对数据进行操作上的方法,请使用线程安全的。
多线程带来的性能问题
上下文切换#
在计算机中,cup的核数其实是远小于线程数的,所以cup先调度多线程的时候会不断的去切换线程,以达到多线程看似在同时执行的场景。但是cup在调度线程的时候,会进行上下文的切换,比如线程1切换到线程2,这时就需要读取线程2的缓存到cpu中,此时的逻辑也会可能跟着线程执行的内容不容而切换执行的逻辑。而这个资源的切换其实全靠cup来完成。这样就会多出一些性能上的消耗。
缓存失效#
在每个线程切换完上下文之后,线程的缓存可能会缓存到CPU的高速缓存中,如果当前线程逻辑还没有执行完成就切换到另外一个线程,那么之前线程的缓存就有可能失效了,那么下次CUP再次调度到这个线程的时候,就又需要重新读取缓存去执行。那么这个时候也会消耗不少的性能资源。
协作开销#
协作开销通常出现在我们为了线程安全上而做的一些操作,比如对一个共享资源进行访问,CUP可能为了保证共享资源的准确性,会禁止CPU的指令重排序。还有可能为了出于同步的目的,反复的把线程工作内存的数据刷新到主存中,然后从主存中刷新到其他线程的工作内存中,这种问题如果发生在单线程上将不会有这种状态,但是处于多线程需要协作,同时又要避免线程安全的问题,就不得不采用上面的方法来牺牲性能。这样就间接的降低了线程的性能。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix