从这篇博文开始,我们开始分享一些多线程的内容,毕竟在工作中,使用多线程比较多。多总结一下,终归没有坏处。这个系列的文章不会特别长,争取在3到5分钟之间结束,主要以说明白内容,给出相应的解决方案,重点在于实践。
如标题所示,这篇博文我们简单的介绍一下内存可见性问题,之前,要简单的介绍一下什么是内存模型?
内存模型
什么是JAVA 内存模型?
Java Memory Model (JAVA 内存模型)是描述线程之间如何通过内存(memory)来进行交互。 具体说来, JVM中存在一个主存区(Main Memory或Java Heap Memory),对于所有线程进行共享,而每个线程又有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。
Java内存模型的抽象示意图如下:
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
1、线程A把本地内存A中更新过的共享变量刷新到主内存中去。
2、线程B到主内存中去读取线程A之前已更新过的共享变量。
说明白了内存模型,我们看一看什么是内存可见性?
内存可见性
内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够立即看到发生的状态变化。
由于线程之间的交互都发生在主内存中,但对于变量的修改又发生在自己的工作内存中,经常会造成读写共享变量的错误,我们也叫可见性错误。
可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
解决方案
我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的volatile变量,还可以使用ReentrantLock,CAS等等。
synchronized关键字
public class TestSynchronized {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true) {
synchronized (td) {
if (td.getFlag()) {
System.out.println("主线程flag:" + td.getFlag());
break;
}
}
}
}
}
class ThreadDemo implements Runnable {
//共享变量
private boolean flag = false;
public boolean getFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
try {
Thread.sleep(200);
} catch (Exception e) {
}
flag = true;
System.out.println("其他线程flag=" + getFlag());
}
}
同步锁方案:会带来性能问题,效率特别低,造成线程阻塞。
volatile关键字
java 提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当多个线程进行操作共享数据时,可以保证内存中的数据可见。 相较于synchronized是一种较为轻量级的同步策略。
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true){
if(td.getFlag()){
System.out.println("主线程flag:" + td.getFlag());
break;
}
}
}
}
class ThreadDemo implements Runnable{
//共享变量
private volatile boolean flag = false;
public boolean getFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
try {
Thread.sleep(200);
} catch (Exception e) {
}
flag = true;
System.out.println("其他线程flag=" + getFlag());
}
}
volatile的读写操作的过程:
(1)线程写volatile变量的过程:
1、改变线程工作内存中volatile变量的副本的值
2、将改变后的副本的值从工作内存刷新到主内存
(2)线程读volatile变量的过程:
1、从主内存中读取volatile变量的最新值到线程的工作内存中
2、从工作内存中读取volatile变量的副本
volatile方案:
1、能够保证volatile变量的可见性
2、不能保证变量状态的"原子性操作(Atomic operations)"
虽然我们对Volatile的内存可见性有了一定理解,我们还需要对它有更加深刻的认识,不再多写了:面试时被问到了volatile ,找个文章总结一下(早点看到就好了)
总结
如果大家既想解决内存可见性又想实现原子性操作,以上两种方案均不可,可以使用lock或CAS,毕竟我们这篇博客,介绍内存可见性,下篇的博文我们会深入的介绍原子操作和cas算法。
后面的博文马上杀到,敬请期待。