Java线程安全问答(草稿)

  1. 什么是线程安全?

如果一个类在多线程执行中,在不考虑运行环境的调度干预,也不需要调用代码的协调同步,仍然保证正确地运行,那么这个类就是线程安全的

也就是说,多线程环境下,线程安全的类总是有正确的行为。但是这种类在实际情况中是很少的。

实际情况下的类一般分为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内存模型的角度分析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原则,这个原则是重排序的一个默认保证(这个如果不保证,则代码的执行结果不可预见,每次执行都可能不同,这是不可接受的):

    • Program order rule. 线程内的代码能够保证执行的先后顺序
    • Monitor lock rule. 对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前
    • Volatile variable rule. 保证前一个对volatile的写操作在后一个volatile的读操作之前
    • Thread start rule. 一个线程内的任何操作必需在这个线程的start()调用之后
    • Thread termination rule. 一个线程的所有操作都会在线程终止之前
    • Interruption rule. 要保证interrupt()的调用在中断检查之前发生
    • Finalizer rule. 一个对象的终结操作的开始必需在这个对象构造完成之后
    • Transitivity. 可传递性

9,这8条原则怎么理解呢?

可以参考

这篇文章
这篇文章
这篇文章

10,双重检查锁定为什么会失效?

双重检查锁定是:


失效的原因主要有:

1.指令重排序

2.内存的可见性
这篇文章

 

你可能感兴趣的:(java,多线程)