喜迎
春节

线程安全


概念

线程安全是指某个方法或某段代码,在多线程中能够正确的执行,不会出现数据不一致或数据污染的情况,我们把这样的程序称之为线程安全的,反之则为非线程安全的。

产生原因

导致线程安全问题的因素有以下 5 个:

  • 多线程抢占式执行
    导致线程安全问题的第一大因素就是多线程抢占式执行,想象一下,如果是单线程执行,或者是多线程有序执行,那就不会出现混乱的情况了,不出现混乱的情况,自然就不会出现非线程安全的问题了。
  • 多线程同时修改同一个变量
    多线程只要不是同时修改同一个变量,也不会出现线程安全问题。
  • 非原子性操作
    原子性操作是指操作不能再被分隔就叫原子性操作。非原子性操作有很多不确定性,而这些不确定性就会造成线程安全问题问题。像 i++ 和 i— 这种操作就是非原子的,它在 +1 或 -1 之前,先要查询原变量的值,并不是一次性完成的,所以就会导致线程安全问题。
  • 内存可见性
    在 Java 编程中内存分为两种类型:工作内存和主内存,而工作内存使用的是 CPU 寄存器实现的,而主内存是指电脑中的内存,我们知道 CPU 寄存器的操作速度是远大于内存的操作速度的。
    那这和线程安全有什么关系呢?这是因为在 Java 语言中,为了提高程序的执行速度,所以在操作变量时,会将变量从主内存中复制一份到工作内存,而主内存是所有线程共用的,工作内存是每个线程私有的,这就会导致一个线程已经把主内存中的公共变量修改了,而另一个线程不知道,依旧使用自己工作内存中的变量,这样就导致了问题的产生,也就导致了线程安全问题。
  • 指令重排序
    指令重排序是指 Java 程序为了提高程序的执行速度,所以会对一下操作进行合并和优化的操作。比如说,张三要去图书馆还书,舍友又让张三帮忙借书,那么程序的执行思维是,张三先去图书馆把自己的书还了,再去一趟图书馆帮舍友把书借回来。而指令重排序之后,把两次执行合并了,张三带着自己的书去图书馆把书先还了,再帮舍友把书借出来,整个流程就执行完了,这是正常情况下的指令重排序的好处。但是指令重排序也有“副作用”,而“副作用”是发生在多线程执行中的,还是以张三借书和帮舍友还书为例,如果张三是一件事做完再做另一件事是没有问题的(也就是单线程执行是没有问题的),但如果是多线程执行,就是两件事由多个人混合着做,比如张三在图书馆遇到了自己的多个同学,于是就把任务分派给多个人一起执行,有人借了几本书、有人借了还了几本书、有人再借了几本书、有人再借了还了几本书,执行的很混乱没有明确的目标,到最后悲剧就发生了,这就是在指令重排序带来的线程安全问题。

解决方法

解决线程安全问题有以下 3 种手段:

  1. 使用线程安全类,比如 AtomicInteger。
  2. 加锁排队执行
    使用 synchronized 加锁。
    使用 ReentrantLock 加锁。
  3. 使用线程本地变量 ThreadLocal。

参考文档

https://mp.weixin.qq.com/s/BKp1c1gMsXWSNy3-8-FH0Q


文章作者: Crazy Boy
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Crazy Boy !
评 论
 上一篇
池化技术
池化技术
概念池化技术指的是提前准备一些资源,在需要时可以重复使用这些预先准备的资源。池化技术的优点主要有两个:提前准备和重复利用。常见的池化技术的应用有:线程池、内存池、数据库连接池、HttpClient 连接池等。
2022-08-02
下一篇 
PHP中define和const的区别
PHP中define和const的区别
在PHP中,定义常量有两种方式: const、define;下面详细说下它们的区别: 1、const是表达式赋值定义一个常量,而define是一个函数,它接受三个参数 2、const对定义的常量大小写敏感,而define可以通过函数的
2022-07-05
  目录