【线程安全】线程安全的注意事项

邓敏 2021年12月14日 77次浏览

有哪些场景需要额外注意线程安全

访问共享变量或资源

当在多线程的环境下,多个线程去访问共享的缓存或者对象时,同时操作会对结果造成不用程度的改变,尤其是在操作上不具备原子性的操作上时会发生这种情况,例如我们之前在【线程安全】 三类线程安全问题章节说到的i + +问题,他其实就属于一种对共享变量访问时,由于i + +不是原子性操作,导致结果不是预期的结果。

依赖时序的操作

在回到i + +的问题上,i + +在cup执行的时候,其实也是一种线程安全的问题。
image
我们再看到这张图,两个线程都同时去执行i + +操作,由于i + +不是原子性操作,这样就会造成第一次i + +还没完成就被第二个线程拿去i + +这样就会操作两次执行的结果一致,但结果就不对了。其实正确的时序性的操作应该是线程1的i + +操作完成之后再去执行i + +的操作。
说到底,如何保证时序的操作,你就保证他这个操作的原子性就可以了,只要让他再执行这段代码的逻辑时,不会被其他的线程抢去执行。

不同数据之间存在绑定关系

这种常发生在业务代码中,比如有两个线程,一个线程获取学生的名字,一个线程获取线程的学号。如果学生的学号变了,学生的名字也会跟着变化,那么这个时候就会容易出现其中一个线程更新不及时,导致学生的名字和学号对不上。导致数据有误。那在这种情况下,我们也是需要去保持线程的原子性。

没有申明自己是线程安全的方法

这种其实在很多新手开发会发生的错误,在使用多线程时,如果使用ArrayList,多个线程同时对它进行数据操作,那么这个时候就会出现数据错误,这个原因就是因为它不是线程安全的,如果需要在多线程的环境下去操作List集合,就需要去使用CopyOnWriteArrayList。
所以在我们日常写多线程的时候,如果用到公共的方法,尤其是在会对数据进行操作上的方法,请使用线程安全的。

多线程带来的性能问题

上下文切换

在计算机中,cup的核数其实是远小于线程数的,所以cup先调度多线程的时候会不断的去切换线程,以达到多线程看似在同时执行的场景。但是cup在调度线程的时候,会进行上下文的切换,比如线程1切换到线程2,这时就需要读取线程2的缓存到cpu中,此时的逻辑也会可能跟着线程执行的内容不容而切换执行的逻辑。而这个资源的切换其实全靠cup来完成。这样就会多出一些性能上的消耗。

缓存失效

在每个线程切换完上下文之后,线程的缓存可能会缓存到CPU的高速缓存中,如果当前线程逻辑还没有执行完成就切换到另外一个线程,那么之前线程的缓存就有可能失效了,那么下次CUP再次调度到这个线程的时候,就又需要重新读取缓存去执行。那么这个时候也会消耗不少的性能资源。

协作开销

协作开销通常出现在我们为了线程安全上而做的一些操作,比如对一个共享资源进行访问,CUP可能为了保证共享资源的准确性,会禁止CPU的指令重排序。还有可能为了出于同步的目的,反复的把线程工作内存的数据刷新到主存中,然后从主存中刷新到其他线程的工作内存中,这种问题如果发生在单线程上将不会有这种状态,但是处于多线程需要协作,同时又要避免线程安全的问题,就不得不采用上面的方法来牺牲性能。这样就间接的降低了线程的性能。