线程安全性定义
多个线程同时访问一个类,不论运行时环境怎样分配调度方法或者这些线程如何交替执行类中的方法,并且调用该类方法的多线程代码不进行同步或协同操作,在这种情况下,这个类表现出的行为都是正确一致的,则称这个线程是安全的。
线程安全性体现的三个方面
原子性提供互斥访问,即同一时刻只能由一个线程访问。
原子性的保障在java中的实现有两种方式:
- atomic包
- 优势:竞争激烈时能维持常态,比Lock性能好
- 劣势:只能同步一个值
- 加锁
- synchronized
- Lock
可见性:一个线程对主内存的修改可以及时被其他线程观察到。
导致共享变量不可见的原因:
- 线程交叉执行
- 重排序结合线程交叉执行
- 共享变量更新后的值没有在工作内存及时更新
JMM关于synchronized的两条规定:
- 线程解锁前,必须把共享变量的最新值刷新到主内存中
- 线程加锁时,必须从主内存中获取到共享变量最新值
volatile通过加入内存屏障和禁止重排序优化来实现可见性:
- 对volatile变量写操作时,会在写操作后加入一条store屏蔽指令,将工作内存的共享变量值刷新到主内存中
- 对volatile变量读操作时,会在读操作前加入一条load屏障指令,将主内存的最新值刷新到工作内存中
volatile关键字的应用:
- 对状态进行标识
- 保证双重检测机制的安全性
有序性,一个线程观察到其他线程中指令的执行顺序,由于指令重排序的存在,该结果一般是杂乱无章的。
有序性实现的规范,happens-before:
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先发生于书写在后面的操作
- 锁定规则:一个unlock操作先行发生于lock操作
- volatile变量规则:对一个volatile变量的写先行发生于volatile读
- 传递规则:a->b,b->c,a->c
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作
- 线程中断规则:对线程的interrupt()方法的调用先行发生于被中断线程的代码检测到中断发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束Thread.isAlive()的返回值检测到线程已经终止执行
- 对象终结规则:一个对象的初始化先行发生于它的finalize()方法的开始
不安全的对象的识别以及怎样预防对象的非线程安全
一个不安全的对象通常作为共享变量而存在,多个线程访问这个对象进行修改导致多次执行结果不一致。
- 线程封闭
- 堆栈封闭:利用变量的局部性保证局部线程安全
- 线程封闭:ThreadLocal绑定对象在特定线程上
- 发布对象:使一个对象能够被当前范围之外的代码所使用
- 对象逸出
- 一种错误的发布方式,对象还没有完成构造时,就使它被其他线程所见
- 一种常见的方式是,构造器内部调用内部类,内部类获取外部类this引用造成线程不安全
- 安全发布对象
- 静态初始化函数中初始化一个对象引用
- 将对象的引用保存到volatile类型域或者AtomicReference对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 不可变对象
- 对象创建后其状态不可变
- 对象所有域都是final类型
- 对象是正确创建的(对象创建期间,this引用没有逸出)
- 一个类的private方法会被隐式的修饰为final方法
- final修饰的变量
- 基本类型final修饰后不可更改
- 引用类型的final修饰变量后引用不可以指向新的对象,但是可以改变原有对象值
- 只用final修饰类属性则不是线程安全的
- 将对象的引用保存到一个由锁保护的域中
- Atomic包
- AtomicInteger
- AtomicLong和LongAddr
- AtomicReference和AtomicReferenceFieldUpdater
- AtomicStampReference
- 解决CAS的ABA问题
Spring框架中@Controller、@Service
Spring框架中Controller和Service默认使用单例,通过单例来重用同一个Controller和Service好处显而易见,但是仔细想想好像会导致线程不安全:
- 如果service类定义了其他成员变量,并且不加额外的保护措施,当controller调用service的方法并有修改service成员变量的操作时,此时多线程并发就暴露出问题了
- 我们平时针对Service编程,都只是在Controller内部调用Service的方法,并没有对Service实例属性(如果有的话) 进行额外的更新操作,所以保证的线程的安全性
- 当我们需要保存其他属性时,可以通过加锁、线程封闭等操作保证对属性的操作是线程安全的