volatile和synchronized特点:
线程安全的两个方面:执行控制可内存可见
执行控制:目的是控制代码执行(顺序)及是否可以并发执行
内存可见:控制的是线程执行结果在内存中对其他线程的可见性,根据java内存模型的实现,线程在具体执行时,会先拷贝主从数据到线程本地(cpu),操作完成后再把结果从线程本地刷到主内存,
synchronized解决的是执行控制的问题,他会阻止其他线程获取当前对象的监控锁,这样就使得当前对象被synchronized关键字保护的代码块无法被其他线程访问,也就无法并发执行,更重要的是synchronized还会创建一个内存屏障,内存屏障保证了所有cpu操作的结果都会刷到主内存中,从而保证了内存的可见性,同时也使得先获得这个锁的线程的所有操作,都happen-before于随后获得这个锁的线程操作。
volatile
关键字解决的是内存可见性的问题,会使得所有对volatile
变量的读写都会直接刷到主存,保证了变量的可见性。这样就能满足一些对变量可见性有要求而对读取顺序没有要求的需求,使用volatile
关键字仅能实现对原始变量(如boolen、 short 、int 、long等)操作的原子性,不能保证复合操作的原子性,如i++
,实际上也是由多个原子操作组成:read i; inc; write i
,假如多个线程同时执行i++
,volatile
只能保证他们操作的i
是同一块内存,但依然可能出现写入脏数据的情况。
使用volatile关键字:
1、对变量的写入操作不依赖于变量的当前值,或者你能确保当前是单线程
2、该变量没有包含在其他变量的不等式中。
-----
volatile和synchronized的区别
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
1、锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。
互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。
可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。(竞态条件)
2、在Java中,为了保证多线程读写数据时保证数据的一致性,可以采用两种方式:
同步:如用synchronized关键字,或者使用锁对象
使用volatile关键字:用一句话概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道。
3、volatile详解
当一个变量定义为volatile后,它将具备两种特性:1. 可见性,2. 禁止指令重排序。
首先:编译器为了加快程序运行速度,对一些变量的写操作会首先在寄存器或CPU缓存上进行,最后写入内存。而在这个过程中,变量的新值对其它线程是不可见的。
可见性:当对volatile标记的变量进行修改时,会将其它缓存中存储的修改前的变量清除,然后重新读取。这里从哪读尚未明确,一般来说应该是先在进行修改的缓存A中修改为新值,然后通知其它缓存清除掉此变量,当其它缓存B中的线程读取此变量时,会向总线发送消息,这是存储新值的缓存A获取到消息,将新值传给B,最后将新值写入内存。
volatile本质是告诉JVM当前变量在寄存器或cpu中的值是不确定的,需要从主存中读取。synchronized则是锁定当前变量,只有当前线程可以访问该变量,其它线程被阻塞。使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。
example:
以i++这一计算过程为例说明为什么volatile不能保证操作的原子性,i++操作可分解为三步:
1.从主存读取i的值到工作内存
2.执行+1运算
3.回写到主存
假使现在有两个线程访问这段代码,i是用volatile修饰的共享变量,初始值为1。线程1在执行到步骤2时,因其他原因(比如中断或者阻塞)让出了CPU使用权,此时线程2获得执行机会执行完这三步后i的值变为2,并将i的值更新到系统内存,接着线程1继续执行,注意此时的线程1并不会从重新从主存读取变量i的值,因为内存屏障(禁止指令重排序)保证了读写操作的顺序,如果这时候再重新从主存读就会引起程序混乱,线程1执行完后把2写到主存中,这和我们期待的结果i=3是不一致的。总而言之:线程只保证刚开始读到的共享变量的值是最新的,从load到store这中间的过程是不安全的,其他线程在这期间修改了共享变量的值,对于已经读取了共享变量的线程而言是不会再读的,后续的计算会延用第一次读取的值,除非还有读操作。