线程与线程安全问题
所有的线程安全问题都可以概况为三个点:原子性,可见性、有序性——————by Java多线程编程实战指南
原子性
原子性就是不可分割,指该操作要么已经发生,要么就没有发生,不会存在中间状态。
这里的观察者是其他的线程,即其他线程不会看到这个线程执行到一半的结果
可见性
可见性的意思是:一个线程对某个共享变量更新后,其他线程访问该变量时可以读取到更新的结果
对于线程安全性问题来说,只保证原子性是完全不够的,只有同时保证原子性和可见性,才能保证一个线程能够正确的看到其他线程对共享变量的更改
ps:父线程在启动子线程之前对共享变量的更新对子线程来说是可见的
有序性
有序性即为保证感知顺序(给定处理器所感知到的内存访问操作发生的顺序)与源代码顺序一致
由于重排序的作用,一个线程对共享变量的更新,可能会对于另一个线程而言变得不可见(例如父线程对共享变量的操作,如果重排序到子线程启动以后,则共享变量对子线程的可见性就不可保证了)。因此有序性可以影响可见性。
线程的活性故障
1.死锁: 鹬蚌相争,谁都不松口
2.锁死: 请求一个不存在的锁 gg
3.活锁: 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
4.饥饿:非公平锁下,有线程一直获取不到锁,导致饥饿
锁
锁的作用
锁的作用,就是为了避免线程安全问题,即保障原子性、可见性和有序性
原子性
锁通过互斥保障原子性,即一个锁只能一个线程持有。这就保障了临界区的代码一次只能被一个线程执行,没有其他线程可以访问,这使得临界区代码所执行的操作具有了不可分割的特性,即保证了原子性。
可见性
在java平台中,锁的获得隐含着刷新处理器缓存这个动作,因此在锁的获得和释放时,线程对共享变量的更新能过被推送到高速缓存中,从而保障了可见性
有序性
由于所对可见性和原子性的保障,在其他线程来看,当前线程对临界区的操作像是在同一时间被更新的,因此其他线程不必知道当前以什么顺序更新变量,即对其他线程来说,当前线程可以认为是按照源代码顺序更新共享变量。
volatile
volatile可以说是一个轻量级的锁,它提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。
volatile变量修饰的共享变量,在进行写操作的时候会多出一个lock前缀的汇编指令,这个指令会触发总线锁或者缓存锁,通过缓存一致性协议来解决可见性问题
对于声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,把这个变量所在的缓存行的数据写回到系统内存,再根据MESI的缓存一致性协议,来保证多CPU下的各个高速缓存中的数据的一致性。
volatile为什么不能保证原子性
对一个原子递增的操作,volatile修饰的变量会分为三个步骤:1.读取volatile变量的值到local;2.增加变量的值;3.把local的值写回让
其他线程可见
(未完待续)