volatile synchronized原理

volatile synchronized原理
 
内存模型:
    计算机在执行程序时,每条指令都是在cpu中执行的,数据是存放在内存中的,因此cpu执行速度很快,而内存读取数据和向内存写入数据速度要慢的多,因此需要cpu中的高速缓存,来代替部分的内存交互,将运算所需要的数据复制一份到cpu的高速缓存中,运算结束后将cache中的数据刷新到内存中。、
        在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存, 存在以下情况
          初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。最后i的结果为1,如果一个变量在多个cpu中都存在缓存,那么就可能出现缓存不一致的问题
 
       为了解决缓存不一致性问题,通常来说有以下2种解决方法:
  1)通过在总线加LOCK#锁的方式
            只能由一个CPU能使用这个变量的内存,在锁住总线期间,其他cpu无法访问内存,导致效率低下
  2)通过缓存一致性协议
             Intel 的 MESI协议:
            MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
  这2种方式都是硬件层面上提供的方式。
 
并发编程中的三个概念:
原子性问题,可见性问题,有序性问题
1、原子性问题
       即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
2、可见性问题
       当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
3、有序性问题
       程序执行的顺序按照代码的先后顺序执行
 
指令重排:
    一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
    处理器在进行重排序时是会考虑指令之间的数据依赖性,重排序不会影响单个线程内程序执行的结果,但是会影响到线程并发执行的正确性。
 
 
Java的内存模型(JMM):
        在Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。
        1、原子性
        在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作。 果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
        2、可见性
         对于可见性,Java提供了volatile关键字来保证可见性。 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
         另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
           3、有序性
             在Java里面,可以通过 synchronized和Lock、 volatile关键字来保证一定的“有序性”。
          另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
 
Happens-before(先行发生)原则:
* 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
    这个规则是用来保证程序在单线程中执行结果的正确性,但无法保证程序在多线程中执行的正确性。
* 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
    无论在单线程中还是多线程中,同一个锁如果出于被锁定的状态,那么必须先对锁进行了释放操作,后面才能继续进行lock操作。
* volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
    如果一个线程先去写一个变量,然后一个线程去进行读取,那么写入操作肯定会先行发生于读操作
* 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
* 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
* 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
* 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
* 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
    
 
Volatile关键字:
    一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
  1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2)禁止进行指令重排序。
    
//线程1
boolean stop = false;
while(!stop){
    doSomething();
}
//线程2
stop = true;
 
用volatile修饰之后就变得不一样了:
  第一:使用volatile关键字会强制将修改的值立即写入主存;
  第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效;
  第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
 
 
volatile保证了可见性,不能保证原子性:
    自增操作(基本数据类型的 自增(加1操作),自减(减1操作)、以及加法操作(加一个数),减法操作(减一个数))不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。
 
volatile在一定程度上能保证有序性:其禁止指令重排
 
Volatile关键字和 synchronized关键字的区别
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值