volatile和synchronized的区别

可见性(Visibility)

可见性就是指当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是volatile的特殊规则保证了新值能立即同步到主内存,以及每使用前立即从内存刷新。因为我们可以说volatile保证了线程操作时变量的可见性,而普通变量则不能保证这一点 。

原子性(Atomicity)

原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。

如果一个操作时原子性的,那么多线程并发的情况下,就不会出现变量被修改的情况。

有序性(Ordering)

Java内存模型中的程序天然有序性可以总结为一句话:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存中主内存同步延迟”现象。

Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则来获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入。

先行发生原则:

如果Java内存模型中所有的有序性都只靠volatile和synchronized来完成,那么有一些操作将会变得很啰嗦,但是我们在编写Java并发代码的时候并没有感觉到这一点,这是因为Java语言中有一个“先行发生”(Happen-Before)的原则。这个原则非常重要,它是判断数据是否存在竞争,线程是否安全的主要依赖。

先行发生原则是指Java内存模型中定义的两项操作之间的依序关系,如果说操作A先行发生于操作B,其实就是说发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包含了修改了内存中共享变量的值、发送了消息、调用了方法等。

volatile和synchronized的区别:

  • volatile是线程同步的轻量级实现, 故volatile性能要比synchronized要好。
  • volatile是变量修饰符,其修饰的变量具有可见性。可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。
    在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主存。而synchronized能修饰方法以及代码块。
  • 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
  • volatile可以禁止进行指令重排。指令重排是指处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。程序执行到volatile修饰变量的读操作或者写操作时,在其前面的操作肯定已经完成,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。
  • volatile可以保证数据的可见性,但不能保证原子性。synchronized可以保证原子性,也阔可以间接保证可见性,因为它会将私有内存和共有内存中数据做同步。(可见性体现在:通过synchronized或者Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存中)
  • 最后:volatile关键是变量在多个线程之间的可见性,而synchronized是解决的是多个线程访问资源的同步性。

分析:volatile保证变量在多个线程之间的可见性,但不具备同步性,也就不具备原子性。volatile出现非线程安全的原因?

  • read和load阶段:从主存复制变量到当前线程工作内存
  • use和assign阶段:执行代码,改变共享变量的值
  • store和write阶段:用工作内存数据刷新到主存对应变量的值
  • 在多线程环境中,use和assign是多次出现的,但这一操作是非原子性,若内存中有count变量,也就是在read和load之后,如果内存count变量发生修改之后,线程工作内存的值已经加载,不会产生对应的变化,也就是私有内存和公共内存中的变量是不同步的,所以也就出现了非线程安全的问题。对于volatile修饰的变量,JVM只保证从主内存到线程工作内存的值是最新的。实质是:volatile解决的是变量读时的可见性问题,但无法保证原子性,所以对于多线程的访问同一个变量还是需要加锁同步的。

Java多线程中提到的原子性和可见性、有序性
Java并发——线程同步Volatile与Synchronized详解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章