文档章节

java并发编程(十五): Java内存模型

ihaolin
 ihaolin
发布于 2014/05/17 14:05
字数 1416
阅读 444
收藏 13

Java内存模型:

  • 了解java内存模型,便于我们更容易理解如安全发布同步策略的规范一致性等机制。

什么是内存模型,为什么需要它

  • JMM规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作何时将对其他线程可见

平台的内存模型

  • JVM通过在适当的位置上插入内存栅栏来屏蔽在JMM与底层平台内存模型之间的差异。

重排序

/**
 * 如果在程序中没有包含足够的同步, 那么可能产生奇怪的结果
 */
public class PossibleReordering {
	static int x = 0, y = 0;
	static int a = 0, b = 0;
	
	public static void main(String[] args) throws InterruptedException {
		Thread one = new Thread(new Runnable(){
			@Override
			public void run() {
				a = 1;
				x = b;
			}
		});
		
		Thread other = new Thread(new Runnable(){
			@Override
			public void run() {
				b = 1;
				y = a;
			}
		});
		
		one.start();
		other.start();
		one.join();
		other.join();
		
		System.out.println("x = " + x + ", y = " + y);
	}
}

由于重排序导致的不确定性:

Java内存模型简介

  • Java内存模型是通过各种操作来定义的,包括变量的读/写操作,监视器的加锁和释放操作,以及线程的启动和合并操作。
  • JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Before。两个操作缺乏Happens-Before关系,则Jvm会对它们进行任意的重排序。
  • Happends-Before的规则包括:

      1. 程序顺序规则。若程序中操作A在操作B之前,则线程中操作A在操作B之前执行。

      2. 监视器锁规则。在同一监视器锁上的解锁操作必须在加锁操作之前执行。如图所示,

               

      3. volatile变量规则。对volatile变量的写操作必须在读操作之前执行。

      4. 线程启动规则。Thread.start必须在线程中执行的操作之前被调用。

      5. 线程关闭规则。线程中的任何操作必须在其他线程检测到该线程结束之前执行,或者从Thread.join中返回,或调用Threas.isAlive返回false。

      6. 中断规则。当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行

      7. 终结器规则。对象的构造函数必须在启动该对象的终结器之前执行完成。

      8. 传递性。如果操作A在操作B之前执行,操作B在操作C之前执行,则操作A必须在操作C之前执行。

借助同步

  • 一个借助同步的例子
public class Sync extends AbstractQueuedSynchronizer {
	private static final int RUNNING = 1, RAN = 2, CANCELLED = 4;
	private Object result;
	private Exception exception;
	
	void innerSet(Object o){
		while (true){
			int s = getState();
			if (ranOrCancelled(s)){
				return;
			}
			if (compareAndSetState(s, RAN)){ //设置状态
				break;
			}
		}
		result = o;
		releaseShared(0); //共享模式下释放锁
		done();
	}
	
	Object innerGet() throws InterruptedException, ExecutionException{
		acquireSharedInterruptibly(0); //共享模式下请求锁
		if (getState() == CANCELLED){
			throw new CancellationException();
		}
		if (exception != null){
			throw new ExecutionException(exception);
		}
		return result;
	}
}
类库中提供的其他Happens-Before排序包括:
  • 将一个元素放入一个线程安全容器的操作将在另一个线程从该容器中获得这个元素的操作之前执行。
  • CountDownLatch上的倒数操作将在线程从闭锁上的await方法中返回之前执行。
  • 释放Semaphore许可的操作将在从该Semaphore上获得一个许可之前执行。
  • Future表示的任务的所有操作将在Future.get中返回之前执行。
  • Executor提交一个Runnable或Callable将在任务开始执行之前执行。
  • 一个线程到达CyclicBarrierExchanger的操作将在其他到达该栅栏或交换点的线程释放之前执行。如果CyclicBarrier使用一个栅栏操作,那么到达栅栏的操作将在栅栏操作之前执行,而栅栏操作又会在线程从栅栏中释放之前执行。

发布

  • 造成不正确发布的真正原因:"发布一个共享对象"与"另一个线程访问该对象"之间缺少一种Happens-Before的关系。

不安全的发布

/**
 * 不安全的延迟初始化
 */
public class UnsafeLazyInitialization {
	private static Object resource;
	
	public static Object getInstance(){
		if (resource == null){
			resource = new Object(); //不安全的发布
		}
		return resource;
	}
}
  • 除了不可变对象以外,使用被另一个线程初始化的对象通常都是不安全的,除非对象的发布操作是在使用该对象的线程开始使用之前执行。

安全的发布

  • Happens-Before排序是在内存访问级别上操作的,它是一种"并发级汇编语言",而安全发布的运行级别更接近程序设计。

安全初始化模式

通过同步实现安全发布,带来一定的性能开销

/**
 * 通过同步安全发布
 */
public class UnsafeLazyInitialization {
	private static Object resource;
	
	public synchronized static Object getInstance(){
		if (resource == null){
			resource = new Object(); //不安全的发布
		}
		return resource;
	}
}
提前初始化实现安全发布:
/**
 * 提前初始化
 */
public class EagerInitialization {
	private static Object resource = new Object();
	
	public static Object getInstance(){
		return resource;
	}
}
类占位实现安全发布:
/**
 * 延长初始化占位类模式
 */
public class ResourceFactory {
	private static class ResourceHolder{
		public static Object resource = new Object();
	}
	
	public static Object getInstance(){
		return ResourceHolder.resource;
	}
}

双重检查加锁

/**
 * 双重检查加锁, 不安全,
 * 线程可能看到无效的值, 可加上volatile修饰
 */
public class DoubleCheckedLocking {
	private static Object resource;
	
	public static Object getInstance(){
		if (resource == null){
			synchronized (DoubleCheckedLocking.class){
				if (resource == null){
					resource = new Object(); 
				}
			}
		}
		return resource;
	}
}

初始化过程中的安全性

/**
 * 不可变对象的初始化安全性
 */
public class SafeStates {
	private final Map<String, String> states; //确保final,以免由于并发而被修改
	
	public SafeStates(){
		states = new HashMap<String, String>();
		states.put("one", "One");
		//...
		states.put("two", "Two");
		//...
	}
	
	public String getAbbreviation(String s){
		return states.get(s);
	}
}
不吝指正。

© 著作权归作者所有

共有 人打赏支持
ihaolin
粉丝 257
博文 164
码字总数 106524
作品 4
朝阳
高级程序员
JVM内存结构 VS Java内存模型 VS Java对象模型

Java作为一种面向对象的,跨平台语言,其对象、内存等一直是比较难的知识点。而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚。比如本文我们要讨论的JVM内存结构、Java内存模型和...

Java架构
07/11
0
0
再有人问你Java内存模型是什么,就把这篇文章发给他。

前几天,发了一篇文章,介绍了一下JVM内存结构、Java内存模型以及Java对象模型之间的区别。有很多小伙伴反馈希望可以深入的讲解下每个知识点。Java内存模型,是这三个知识点当中最晦涩难懂的...

Java架构
07/11
0
0
再有人问你Java内存模型是什么,就把这篇文章发给他

网上有很多关于Java内存模型的文章,在《深入理解Java虚拟机》和《Java并发编程的艺术》等书中也都有关于这个知识点的介绍。但是,很多人读完之后还是搞不清楚,甚至有的人说自己更懵了。本文...

java高级架构牛人
07/04
0
0
终于有人把Java内存模型说清楚了!

本文就来整体的介绍一下 Java 内存模型,读完本文以后,你就知道到底 Java 内存模型是什么,为什么要有 Java 内存模型,Java 内存模型解决了什么问题等。 本文中很多说法都是笔者自己理解后定...

51CTO技术栈
07/25
0
0
Java内存模型是什么,为什么要有Java内存模型,Java内存模型解决了什么问题?

网上有很多关于 Java 内存模型的文章,但是很多人读完之后还是搞不清楚,甚至有的人说自己更懵了。 本文就来整体的介绍一下 Java 内存模型,读完本文以后,你就知道到底 Java 内存模型是什么...

Java大蜗牛
07/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JS三元运算示例

1. topFlag=topFlag ==0?1:0; 等于 if(topFlag=00){ topFlag=1; }else if(topFlag == 1){ topFlag=0; } 2. 5>3?alert('5大'):alert('3大'); 即 if(5>3){alert('5大')}else{alert('3大')}; 注......

森火
今天
0
0
利用Slf4j的MDC跟踪方法调用链

why? 一个web项目通常提供很多URL访问地址, 项目一般都是分层处理,例如Controller——>Service——>DAO。 如果想根据日志查看用户一次请求都走了哪些方法(多数是查错误)。 如果系统是多人...

杨春炼
今天
9
0
Maven介绍及安装

Maven介绍及安装 以下内容是本人早期学习时的笔记,可能比较详实繁琐,现在复习一下Maven,顺便将内容抛出来,供大家一起学习进步。 一、Maven简介 Maven是Apache旗下的一款项目管理工具,是...

星汉
今天
0
0
小程序Aes解密

主要步骤: 1、下载AES源码(JS版) 2、在小程序中新建一个公共的文件夹,把AES源码拷贝进去(注意:需要暴露接口 module.exports = CryptoJS;) 3、添加一个用于加密解密的公共JS,可取名为...

Mr_Tea伯奕
今天
0
0
Go实现文件传输(基本传输可用)

发送端 package mainimport ("fmt""os""net""io")func SendFile(path string, connect net.Conn){file, oerr :=os.Open(path)if oerr !=nil{fmt.Println("Open", oerr)......

CHONGCHEN
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部