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