起手篇
我们今天来介绍一下java面试中最常会被面试官提到的问题,也是Java多线程中经常被问到的问题:synchronized和volatile的区别,希望能够帮助到Java相关方面的求职者
Java内存模型(JMM)
提到这两个有关于线程的关键字,那么我们不得不提到Java的内存模型了(JMM),下面我们先看一下Java内存模型在处理多线程方面的工作原理图。
Java内存模型(java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节
首先介绍两个概念
- 可见性:一个线程对共享变量值的修改,能够及时地被其他线程看到。
- 共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
共享变量可见性实现的原理
线程1对共享变量的修改要想被线程2及时看到,必须要经过如下两个步骤
- 把工作内存1中更新过的共享变量刷新到主内存中
- 将主内存中最新的共享变量的值更新到工作内存2中
下图为一个共享变量实现可见性原理的一个示例:
其中,线程对共享变量的操作,遵循一下两条规则
- 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写
- 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成
可见性
要实现共享变量的可见性,必须保证两点:
- 线程修改后的共享变量值能够及时从工作内存刷新到主内存中
- 其他线程能够及时把共享变量的最新值从主内存更新到自己的工作内存中
可见性的实现方式:
- synchronized
- volatile
synchronized实现可见性:
- 原子性(同步)
- 可见性
JMM关于synchronized的两条规定:
- 线程解锁前,必须把共享变量的最新值刷新到主内存中
- 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时,需要从主内存中重新读取最新的值(注意:加锁与解锁需要是同一把锁)
注意:线程解锁前对共享变量的修改在下次加锁时对其他线程可见
线程执行互斥代码的过程:
1、获得互斥锁
2、清空工作内存
3、从主内存拷贝变量的最新副本到工作的内存
4、执行代码
5、将更改后的共享变量的值刷新到主内存
6、释放互斥锁
思考?
public class Demo {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
while (true) {
if (myThread.flag) {
System.out.println("主线程读到flag=" + myThread.flag);
break;
}
}
}
}
class MyThread extends Thread {
public boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("MyThread线程修改flag=" + flag);
}
}
PS:上面的代码为什么会出现死循环呢?分析:首先大家都知道如果一个主线程里面有个子线程,那么百分之85以上都是先执行主线程 然后再执行子线程 ;这里是因为线程之间都是隔离的 都有自己的工作内存,彼此之间是不可见的,解决方式就是上面说的要不在变量处加个volatile或者在代码处加synchronized