多线程学习(5)volatile 和 synchronized 的区别

自闭症网瘾萝莉.ら 提交于 2019-12-10 09:59:51

满足同步三个基本条件:

1.原子性

原子性是指操作是不可分的。其表现在于对于共享变量的某些操作,应该是不可分的,必须连续完成。例如a++,对于共享变量a的操作,实际上会执行三个步骤,1.读取变量a的值  2.a的值+1  3.将值赋予变量a 。 这三个操作中任何一个操作过程中,a的值被人篡改,那么都会出现我们不希望出现的结果。所以我们必须保证这是原子性的。

2.可见性:一个线程对共享变量值得修改,能够及时的被其他线程看到 

多线程高并发中,线程之间的通信是靠内存共享,即一个对象或变量,它在堆中的主内存中,每一个请求可以看做一个线程,每一个线程,都想操作这个主内存的对象或变量,线程会复制一份主内存中的对象或变量,到自己的线程本地栈内存中,直到操作完毕,才会将本地栈内存的对象或变量,赋值给主内存。如果不加同步锁,主内存中的对象或变量,值就容易被最后提交操作的线程覆盖,发生错误。

3.有序性:

指令重排序:代码书写的顺序与实际执行的顺序不同,指令重排序是编译器或处理器为了提高程序性能而做的优化。

1.编译器优化的重排序(编译器优化)

2.指令级并行重排序(处理器优化)

3.内存系统的重排序(处理器优化)

是不是所有的语句的执行顺序都可以重排呢?

答案是否定的。为了讲清楚这个问题,先讲解另一个概念:数据依赖性

什么是数据依赖性?

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖。数据依赖分下列三种类型:

名称 代码示例 说明
写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
读后写 a = b;b = 1; 读一个变量之后,再写这个变量。

上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。所以,编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。也就是说:在单线程环境下,指令执行的最终效果应当与其在顺序执行下的效果一致,否则这种优化便会失去意义。这句话有个专业术语叫做as-if-serial semantics (as-if-serial语义)

int num1=1;//第一行
int num2=2;//第二行
int sum=num1+num;//第三行

单线程:第一行和第二行可以重排序,但第三行不行

重排序不会给单线程带来内存可见性问题

多线程中程序交错执行时,重排序可能会照成内存可见性问题。

 

synchronized关键字

synchronized通过操作对象锁和类对象锁,修饰方法和代码块,则保证持有锁的线程操作完毕,重新赋值主内存中的对象或变量后,其他线程再去争抢锁,继续操作主内存对象。达到一个线程的执行结果对其他线程的。满足上面的三个原则。

 

volatile变量每次被线程访问时,都强迫从主内存中读取该变量的值,而当变量发生变化的时候都会强迫线程将最新的值刷新到主内存中。

这样不同的变量总能看到最新的值。

可以把volatile变量的单个读写,看成是使用同一个锁对这些单个读/写操作做了同步。

volatile关键字:

  • 能够保证volatile变量的可见性
  • 只能保证单个volatile变量的原子性,对于volatile++这种复合操作不具有原子性
  • 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令

    • store指令会在写操作后把最新的值强制刷新到主内存中。同时还会禁止cpu对代码进行重排序优化。这样就保证了值在主内存中是最新的。
  • 对volatile变量执行读操作时,会在读操作前加入一条load屏障指令

    • load指令会在读操作前把内存缓存中的值清空后,再从主内存中读取最新的值。

 

 

synchronized和volatile的比较:

  • synchronized锁住的是变量和变量的操作,而volatile锁住的只是变量,而且该变量的值不能依赖它本身的值,volatile算是一种轻量级的同步锁
  • volatile不需要加锁,比synchronized更加轻量级,不会阻塞线程。
  • 从内存可见性角度讲,volatile读相当于加锁,volatilexie相当于解锁。
  • synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。

注:由于voaltile比synchronized更加轻量级,所以执行的效率肯定是比synchroized更高。在可以保证原子性操作时,可以尽量的选择使用volatile。在其他不能保证其操作的原子性时,再去考虑使用synchronized。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!