此篇接上一篇Java面试之多线程状态(三)
对于线程安全问题,想从是什么、为什么、怎么做三个方面来整理这篇知识点。
首先,在《Java并发编程实战》书中,Brian Goetz大神是这样定义什么是线程安全性的。
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
简单来说,在多个线程访问某个方法或者对象的时候,不管通过任何的方式调用以及现场如何去交替执行。在程序中不做任何同步干预操作的情况下,这个方法或者对象的执行或修改都能按照预期的结果来反馈,那么这个类就是线程安全的。
实际上,线程安全问题的具体表现体现在三个方面,原子性,可见性,有序性。
原子性,是指当一个线程执行一系列程序指令操作的时候,它应该是不可中断的,因为一旦出现中断,站在多线程的视角来看,这一系列的程序指令会出现前后执行结果不一致的问题。
这个和数据库里面的原子性是一样的,简单来说就是一段程序只能由一个线程完整的执行完成,而不能存在多个线程干扰。
CPU的上下文切换,是导致原子性问题的核心,而JVM里面提供了synchronized关键字来解决原子性问题。
可见性,就是说在多线程环境下,由于读和写是发生在不同的线程里面,有可能出现某个线程对共享变量的修改,对其他线程不是实时可见的。
导致可见性问题的原因有很多,比如CPU的高速缓存、指令重排序,编译器的指令重排序。
有序性,指的是程序编写的指令顺序和最终CPU运行的指令顺序可能出现不一致的现象。这种现象也可以称为指令重排序,
所以有序性也会导致可见性问题。可见性和有序性可以通过JVM里面提供的一个volatile关键字来解决。
线程安全问题只在多线程环境下才出现,单线程串行执行不存在此问题。保证高并发场景下的线程安全,可以从以下四个维度考量:
1.数据单线程内可见。单线程总是安全的。通过限制数据仅在单线程内可见,可以避免数据被其他线程篡改。最典型的就是ThreadLocal线程局部变量,它存储在独立虚拟机栈的局部变量表中,与其他线程毫无瓜葛。
2.只对对象。只读对象总是安全的。它的特性是允许复制、拒绝写入。最典型的只读对象有String、Integer等。一个对象想要拒绝任何写入,必须要满足以下条件:使用final关键字修饰,避免被继承;使用private final关键字避免属性被中途修改;没有任何更新方法;返回值不能为可变对象。
3.线程安全类。某些线程安全类的内部有非常明确的线程安全机制。比如StringBuffer就是一个线程安全类,它采用synchronized关键字来修饰相关方法。
4.同步与锁机制。如果想要对某个对象进行并发更新操作,但又不属于上述三类,需要自己在代码中实现安全的同步机制。虽然这个机制支持的并发场景很有价值,但非常复杂且容易出现问题。
注:此篇知识整理自《Java并发编程实战》、《咕泡》、《Java并发编程之美》
喜欢的朋友记得点赞、收藏、关注哦!!!