线程基础知识03-volatile和synchronized

参考书:《Java并发编程艺术》

在学习这一块知识之前,可以先学习一下JMM相关的知识,回过来再看这个问题,就很好理解:https://www.cnblogs.com/perferect/p/13680158.html

volatile

特性

  • 可见性:对一个volatile变量的读,总能看到任意线程对这个volatile变量最后的写入。

  • 原子性:对于一个volatile修饰的变量的读/写具有原子性,但是类似于volatile++ 这种复合操作不具有原子性

注意:很多博客都写volatile变量不具有原子性,这是他们对上面原子性这段话产生了错误的理解

举个例子

volatile  int a = 0;

上面也就是说:

如果只是 a =10; 这种写入操作时没有问题的;

但是 a = a  + a * 10 + 1 ; 这种同时进行读和写的操作时无法保证对volatile变量操作的原子性的;

至于什么原因?

  • 其实我们在学JMM的时候,也知道了,在volatile的读和写之前进行的时不同的屏障,而这种复合操作,显然是很多个步骤的读和写,需要进行“区域管理”,这也就是下面我们要了解的synchronized要做的事情了

volatile的原理

编译器重排序的顺序一致性约束

  • 正如我们前面学习到的JMM中,编译器重排序,通过对volatile变量,读和写增加内存屏障来约束

转成可执行代码后的缓存一致性

  • 通过Lock前缀,引起处理器缓存回写到内存
其实指是通过#LOCK指令锁定内存中的缓存并写回到内存区,并使用缓存一致性机制来保证修改的原子性。此操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改有两个以上处理器缓存的内存区间。
  • 利用嗅探技术,保证一个处理器的缓存回写到内存中其他处理器的缓存会变成无效。

synchronized

  • JMM编译器重排序

    • 锁通过创建临界域,限制域外的代码和域内部代码的重排序,避免临界域内的变量“逸出”域。
  • 生成指令执行过程中

    • 底层是通过在方法的插入点通过monitorenter和monitorexit进行匹配。

synchronized中的锁:

  • 对于普通同步方法,锁是当前实例对象。

  • 对于静态同步方法,锁是当前类的Class对象

  • 对于同步方法快,锁是Synchonized括号里配置的对象

总结

1. volatile和synchronized的区别

  • JMM编译器重排不同:

    • volatile:是通过在volatile变量的独写操作前后增加屏障实现防止重排序的

    • synchronized: 是通过增加临界区,防止临界区内变量逸出。

  • 执行指令的不同:

    • volatile:通过#LOCK前缀,刷新数据到主内存中

    • synchronized:是通过monitor,对指令块进行监听,通过monitorenter和monitorexit进行匹配

原文地址:https://www.cnblogs.com/perferect/p/13679249.html