1、JMM
通俗地讲,就是描述Java中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。
JMM对共享内存的操作做出了如下两条规定:
线程对共享内存的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写;
不同线程无法直接访问其他线程工作内存中的变量,因此共享变量的值传递需要通过主内存完成。
2、内存的可见性
可见:一个线程修改了这个变量的值,在另外一个线程中能够读到这个修改后的值。
JMM规定变量在线程之间是不可见的,也就是说假如线程1修改了变量a的值,如果线程2中的工作内存区域存在a的副本的话,那么线程2是无法知道线程1将a修改为什么值的。
java提供了两种方式来保障内存的可见性
- 1、使用synchronized实现可见性
- 2、使用volatile实现可见性
3、使用synchronized实现内存可见性
Java中synchronized关键字有两重含义,一是实现原子性,二就是实现内存可见性。
synchronized可见性规范:
- 线程加锁时,将清空工作内存中共享变量的值,从而需要从主内存中重新读取最新值。
- 线程解锁前必须把共享变量的最新值刷新到主内存中。
也就是说,在线程代码中,只要出现synchronized代码块,那么在同步代码块下面的代码获取变量时都是从主存中直接获取变量的值,因为在进入同步代码块时,线程会清空工作内存中共享变量的值。
以下代码将由于变量内存不可见的缘故,程序将进入死循环,因为对于线程2来说,run的值永远是ture。
public class Demo2 { public boolean run = true; public static void main(String[] args) { Demo2 d = new Demo2(); // 线程1 new Thread(new Runnable() { @Override public void run() { // 此处睡眠,让下面的线程2先运行,线程2将会在工作内存中缓存run的值为true try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 因为线程2缓存了run的值,所以此时线程1修改run的值,线程2依然不知道 // 如果是本线程先将run的值赋为false的话,那么线程2运行时获得的值就会为false了 d.run = false; } }).start(); // 线程2 new Thread(new Runnable() { @Override public void run() { // 在工作内存中缓存run的值为true while (d.run) { } System.out.println("线程2执行了..."); } }).start(); } }
假如将线程2改成如下代码,则线程2将会跳出while循环
// 线程2 new Thread(new Runnable() { @Override public void run() { while (d.run) { System.out.println(d.run); } System.out.println("线程2执行了..."); } }).start();
原因是println方法是一个带有synchronized代码块的方法,而当线程2执行了println方法之后,run的值将会从线程的工作内存区中清除掉,并从主存中获取最新的值。
public void println(boolean x) { synchronized (this) { print(x); newLine(); } }
4、使用volatile实现可见性
Java中的volatile可以保证volatile变量的可见性,但不保证复合操作的原子性(如++)
volatile可见性规范如下:
对volatile变量执行写操作时,会在写操作后加入一条store写屏障指令,强制将缓存刷新到主内存中。
对volatile变量执行读操作时,会在读操作前加入一条load读屏障指令,强制使缓冲区缓存失效,所以会从主内存读取最新值。
防止指令重排序。
通俗来讲:volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存。这样在任何时刻,不同的线程总能看到该变量的最新值。
synchronized与volatile对比:
- volatile不需要加锁,比synchronized轻量,不会阻塞线程。
- 从内存可见性角度来看,volatile读相当于加锁,volatile写相当于解锁。
- synchronized可以保证可见性+原子性。volatile只能保证可见性,不能保证原子性。
在上述代码中,只需将run变量定义成如下代码所示,线程2则会跳出循环
public volatile boolean run = true;
5、参考资料
https://www.cnblogs.com/IcePeak/p/4450972.html