多线程之内存可见性Volatile(一)

从这篇博文开始,我们开始分享一些多线程的内容,毕竟在工作中,使用多线程比较多。多总结一下,终归没有坏处。这个系列的文章不会特别长,争取在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算法。

后面的博文马上杀到,敬请期待。

©️2020 CSDN 皮肤主题: 创作都市 设计师: CSDN官方博客 返回首页
实付5.20元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值