码迷,mamicode.com
首页 > 编程语言 > 详细

Java多线程之内存可见性

时间:2015-04-06 15:46:53      阅读:271      评论:0      收藏:0      [点我收藏+]

标签:java   线程   可见性   原子性   volatile   

可见性:一个线程对共享变量值的修改,能够及时的被其它线程看到。

共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。

所有的变量都存储在主内存中


JAVA内存模型(JVM)

  • 所有的变量都存储在主内存中
  • 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)


两条规定

  • 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写。
  • 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。


共享变量可见性实现的原理
线程1对共享变量的修改要想被线程2及时看到,必须要经过如下2个步骤:

  • 把工作内存1中更新过的共享变量刷新到主内存中
  • 将主内存中最新的共享变量的值更新到工作内存2中
技术分享

要实现共享变量的可见性,必须保证两点:
  • 线程修改后的共享变量值能够及时从工作内存刷新到主内存中
  • 其它线程能够及时把共享变量的最新值从主内存更新到自己的工作内存中

可见性的实现方式
Java语言层面支持的可见性实现方式:
  • synchronized
  • volatile

synchronized实现可见性
synchronized能够实现:
  • 原子性(同步)
  • 可见性
JVM关于synchronized的两条规定:
  • 线程解锁前,必须把共享变量的最新值刷新到主内存中
  • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁需要是同一把锁)
线程执行互斥代码的过程:
  1. 获得互斥锁
  2. 去年清空工作内存
  3. 从主内存拷贝变量的最新副本到工作内存
  4. 执行代码
  5. 将更改后的共享变量的值刷新到主内存中
  6. 释放互斥锁
public class SynchronizedDemo {
	private boolean ready = false;
	private int result = 0;
	private int number = 1;
	/**
	 * 写操作
	 */
	public void write(){
		ready = true;			//1.1
		number = 2;				//1.2
	}
	/**
	 * 读操作
	 */
	public void read(){
		if(ready){				//2.1
			result = number*3;	//2.2
		}
		System.out.println("result的值为:"+result);
	}
	private class ReadWriteThread extends Thread{
		private boolean flag;
		public ReadWriteThread(boolean flag){
			this.flag = flag;
		}
		@Override
		public void run(){
			if(flag){
				//构造方法中传入true,执行写操作
				write();
			}else{
				//构造方法中传入true,执行读操作
				read();
			}
		}
	}
	public static void main(String[] args) {
		SynchronizedDemo demo = new SynchronizedDemo();
		//启动线程执行写操作
		demo.new ReadWriteThread(true).start();
		//启动线程执行读操作
		demo.new ReadWriteThread(false).start();
	}
}

volatile实现可见性
volatile关键字:
  • 能够保证volatile变量的可见性
  • 不能保证volatile变量复合操作的原子性
volatile如何实现内存可见性:
深入来说:通过假如内存屏障和禁止重排序优化来实现的。
  • 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
  • 对volatile变量执行读操作时,会在读操作前加入一条load屏障指令
通俗的讲:volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,
而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存中。
这样任何时刻,不同的线程总能看到该变量的最新值。

线程写volatile变量的过程:
  1. 改变线程工作内存中volatile变量副本的值
  2. 将改变后的副本的值从工作内存刷新到主内存
线程读volatile变量的过程:
  1. 从主内存中读取volatile变量的最新值到线程的工作内存中
  2. 从工作内存中读取volatile变量的副本
volatile不能保证volatile变量符合操作的原子性:
private int number= 0;                                          synchronized(this){
number++:  不是原子操作                                                number++;
  1. 读取number的值 }
  2. 将number的值加1 加入synchronized,变为原子操作
  3. 写入最新的number的值 private  volatile int number = 0; 
变为volatile变量,无法保证原子性
public class VolatileDemo {
	private volatile int number = 0;
	public int getNumber(){
		return this.number;
	}
	public void increase(){
		this.number++;
	}

	public static void main(String[] args) {
		final VolatileDemo demo = new VolatileDemo();
		for(int i = 0;i<500;i++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					demo.increase();
				}
			}).start();;
		}
		//如果还有子线程在运行,主线程就让出cpu资源
		//直到所有子线程都运行完了,主线程再继续往下执行
		while(Thread.activeCount()>1){
			Thread.yield();
		}
		System.out.println("number:"+demo.getNumber());
	}
}


synchronized和volatile比较
  • volatile不需要加锁,比synchronized更轻量级,不会阻塞线程
  • 从内存可见性角度讲,volatile读相当于加锁,volatile写相当于解锁
  • synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性


Java多线程之内存可见性

标签:java   线程   可见性   原子性   volatile   

原文地址:http://blog.csdn.net/lovesomnus/article/details/44901443

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!