一、原子性
1、原子性基本概念
原子性:对于涉及共享变量访问的操作,若 该操作从其执行线程以外的任何线程来看是不可分割的,那么该操作就是原子操作,称其操作具有原子性。
(1)不可分割含义
- 一个操作具备原子性也就消除了这个操作导致竞态的可能性。
含义一:指访问(读、写)某一个共享变量的操作从其执行线程以外的任何线程来看,该操作要么执行结束,要么尚未发生,也就是说其他线程不会看到该执行操作的中间结果。
含义二:同一组共享变量的原子操作不能够交错的,排除一个线程在执行一个操作期间另一个线程读取或者更新该操作所访问的共享变量而导致的干扰(读取脏数据)和冲突(丢失更新)的可能。
(2)发生原型性问题的必须条件
条件一:原子性操作是对共享变量操作而言的
条件二:原子性操作是从该操作线程以外的线程来描述的,也就是说只有在多线程环境下面采用意义。
(3)java中实现原子性操作的方式
方式一:使用锁,锁具有排他性,即它能够保障一个共享变量在任何时刻只能被一个线程所访问,也就消除了多个线程在同一个时刻访问同一共享变量而导致干扰与冲突的可能,也就消除了竞态
方式二:利用处理器提供的CAS指令,CAS是直接在硬件(处理器和内存)这一层次实现的,因此也叫做硬件锁,其原理和锁类似。
(4)java语言层面提供对原子性的支持
1、除long和double类型以外的任何基本类型和引用类型的写操作都是原子性的。由java语言规范定义,jvm具体实现。
2、特别规定对于valotile修饰的long和double型变量的写操作具有原子性,但是volatile关键字并不能保证其他操作(read-modify-write操作和check-then-act操作)的原子性,我们可以通过使用java语言提供的机制来使其变成原子性操作,比如使用前面说的锁和CAS指令。
3、java语言中的任何类型的读操作都是原子性的(long和double在jdk1.5之后读操作也是原子性的)
4、注意,在java语言中,原子性操作+原子性操作!=原子性操作
二、可见性
可见性:在多线程环境下面,一个线程对某一个变量进行更新之后,后续访问该变量的线程可能无法立即读取到这个更新后的结果,甚至永远也无法读取到这个更新结果。也就是说可见性问题就是指一个线程对共享变量的更新的结果对于读取相应共享变量的线程而言是否可见的问题。
(1)可见性问题产生的根源
可见性问题产生的根源于计算机存储系统有关,具体包括的概念有(处理器、主内存、高速缓存、写缓冲器、无效化队列等),具体可以参考高速缓存与硬件基础。
(2)解决可见性问题策略
缓存一致性协议
缓存同步:一个处理器从其自身处理器缓存以外的其他存储部件中读取数据并将其更新到该处理器的高速缓存中。
冲刷处理器缓存:使一个处理器对共享变量所做的更新最终被写入该处理器高速缓存或者主内存中,这个过程称为冲刷处理器缓存。
刷新处理器缓存:一个处理器在读取共享变量时候,如果其他的处理器再此之前已经更新了该变量,那么该处理器必须从其他处理器的高速缓存中或者主内存中对相应的变量进行缓存同步,这个过程称为刷新处理器缓存
- 因此可见性的保证是通过使更新共享变量的处理器执行冲刷处理器缓存的动作,并使读取共享变量的处理器执行刷新处理器缓存的动作来实现的。
(3)java中如何保证可见性
1、使用锁
2、对共享变量使用volatile修饰。volatile所起的作用主要分为两点(1)阻止编译器做出可能导致程序运行不正常的优化(2)使冲刷处理器缓存和刷新处理器缓存得以执行
(4)可见性需要注意的问题
1、可见性保证仅仅意味着一个线程能够读取到共享变量的相对新值,而不能够保证读取到相应共享变量的最新值。
相对新值:对于一个共享变量而言,一个线程更新了该变量的值后,其他线程能够读取到这个更新后的值,那么这个值就称为相对新值
最新值:如果读取这个变量的线程在读取并使用该变量的时候其他线程无法更新该变量的值,那么该变量读取到的相对变量的值就称为最新值。
2、单核处理器系统实现的多线程编程也是可能出现可见性问题的
(5)可见性和原子性比较
- 原子性描述是一个线程对共享变量的更新,从另一个线程的角度看,它要么完了,要么尚未发生,而不是进行中的一种状态。也就是说原子性保证了一个线程读取到的共享变量的值要么是初始值,要么是该变量的相对新值,不可能是更新过程中的一个“半成品”值。
- 可见性描述的是一个线程对共享变量的更新 对于另一个线程而言是否可见的问题,保证可见性意味着一个线程读取到相应共享变量的相对新值。
三、有序性
有序性:一个处理器上运行的一个线程所执行的内存访问操作在另一个处理器上运行的其他线程看来是乱序的。
重排序:一个处理器执行多个操作,从其他处理器角度来看顺序可能与目标代码执行的顺序不一致,这个现象为重排序
重排序是对内存有关的操作(读和写)所做的一种优化,可以在不影响单线程程序正确性的情况下面提升程序的性能 ,但是可能会对多线程程序的正确性产生影响。重排序的来源很多,包括编译器、处理器、存储子系统(比如写缓冲区、高速缓存等)
指令重排序:在源代码顺序和程序顺序不一致,或者程序顺序和执行顺序不一致,就陈发生了指令重排序。编译器和处理器都会导致指令重排序,两者目的都是为了提高指令的执行效率。
存储子系统重排序(内存重排序):即时处理器严格按照程序的顺序执行两个内存访问操作的情况下面,在存储子系统作用下其他处理器对这两个操作的感知顺序任然可能与程序顺序不一致,即这两个操作执行顺序看起来发生了变化。
(1)保证有序性
- 从底层的角度看,禁止重排序是通过调用处理器提供的相应指令(内存屏障)来实现的,而java层面,相应的提供了语言层面的解决方案,通过volatile和sychronized关键字都可以解决有序性问题。
(2)有序性和可将性关系
- 可将性是有序性的基础
- 有序性影响可见性
网友评论