如果一个类在多线程执行中,在不考虑运行环境的调度干预,也不需要调用代码的协调同步,仍然保证正确地运行,那么这个类就是线程安全的
也就是说,多线程环境下,线程安全的类总是有正确的行为。但是这种类在实际情况中是很少的。
实际情况下的类一般分为5个类别(Java Concurrency in Practice》的作者Brian Goetz给出):
这篇文章的最后给出了这5个级别的更多解释。
?2.为什么也好关注线程安全?
随着软硬件的发展,Java代码运行在多处理器环境中。
2.1 spring单例,tomcat线程池
spring容器中,类多是单例的。
而web服务器为了提高性能,都是用了线程池处理请求。
2.2提升效率,自己开启新线程
为了提升效率,
应用中也会存在开启多个线程分隔处理多个任务。
或者自己维护线程池异步处理任务的情况。
此时,多个线程的协调和交互都需要关注线程安全。
java中类中的属性会同时有多个线程访问。
3.为什么是类中的属性?方法参数不需要关注线程安全吗?
JVM内存结构图
?
是属性还是方法参数取决于在java运行时共享的资源
这篇文章讲述了java运行时共享的那个资源。
这篇文章从java内存模型的角度分析java中属性和方法参数在底层的实现。
4.怎么才能保证线程安全呢?
需要保证 原子性,可见性,有序性。
5.什么是原子性?为什么要考虑原子性?
一个线程可能对某个属性有多个操作,比如i=i++。可以分解为2个动作
原子性就是这个操作在2步操作中是都必须要完成的,中间不能有其他线程的操作。对其他线程来说这个操作是排他的。
这个保证原子性的过程,需要锁的参与。除了排他锁,还有其他锁。
这篇文章讲的比较详细,也比较有难度。实际上java已经提供了锁的实现,直接使用即可。
加锁同时保证了有序性和可见性。
7,什么是可见性?为什么要考虑可见性?
可见性是值一个线程的操作结果对其他线程可见。
为什么会有不可见的情况呢?这是由java的内存模型决定的,java的内存模型是一个抽象的概念,其具体组成是由cpu缓存,指令缓存等构成的。
为了不受其他因素影响,提升效率,java为每一个线程分配了内存空间叫“工作内存”,执行过程中,
每个线程会从“主内存”拷贝一份数据到工作内存。并在工作内存中计算完成之后将计算结果回写到主内存。比如i++操作。
多个线程的工作内存是不可见的,只有在主内存的数据才可见。
java中的关键字volatile 抑制了一些线程运行上的优化,保证了可见性。比如对volatile变量的操作,任何线程的操作都不再拷贝数据到自己的工作内存,而是直接在主内存中操作。这样保证了多个线程的操作都是可见的,同时也保证了部分的有序性,但是降低了效率。
JVM中每个线程会有自己的栈,而堆是存放各线程所用对象的地方。堆类似于JMM中的主内存,栈中的一部分类似工作内存。
8 .什么是有序性?为什么要考虑有序性?
java为了线程的高效并发执行,也是为了配合工作内存或者说缓存的有效利用,除了工作内存机制的优化之外,还有“指令重排序”。所以需要考虑有序性。
有序性在多线程环境中比较复杂,有序性是指某个操作(操作B)的结果可能需要前一个操作(操作A)的结果前提下完成。
正确的结果应该是操作A->操作B.不应该出现这种情况操作B->操作A.
本线程内观察,线程内的所有操作有序,一个线程观察另外一个则无序。
多线程环境中保证有序性,需要遵循happens-before原则,这个原则是重排序的一个默认保证(这个如果不保证,则代码的执行结果不可预见,每次执行都可能不同,这是不可接受的):
9,这8条原则怎么理解呢?
可以参考
这篇文章
这篇文章
这篇文章
10,双重检查锁定为什么会失效?
双重检查锁定是:
失效的原因主要有:
1.指令重排序
2.内存的可见性
这篇文章
?