文档章节

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

ihaolin
 ihaolin
发布于 2014/05/17 14:05
字数 1416
阅读 446
收藏 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
粉丝 259
博文 164
码字总数 106524
作品 4
朝阳
高级程序员
私信 提问
基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程

许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存、CPU、缓存等予以说明。实际上,在实...

leoliu168
11/08
0
0
终于有人把Java内存模型(JMM)说清楚了

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

消失er
08/05
0
0
JVM内存结构 VS Java内存模型 VS Java对象模型

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

Java架构
07/11
0
0
Java内存模型是什么,为什么要有Java内存模型,Java内存模型解决了什么问题?

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

Java大蜗牛
07/26
0
0
Java面试:投行的15个多线程和并发面试题

本文由ImportNew -一杯哈希不加盐 翻译自dzone。欢迎加入翻译小组。转载请见文末要求。 多线程和并发问题已成为各种 Java 面试中必不可少的一部分。如果你准备参加投行的 Java 开发岗位面试,...

ImportNew
08/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Confluence 6 快捷键

快捷键图标。 官方的下载地址为:https://atlassianblog.wpengine.com/wp-content/uploads/2018/01/keyboard-shortcuts-infographics.pdf...

honeymose
今天
2
0
Apache限定目录解析PHP,限制user_agent,PHP相关的配置

Apache限定目录解析PHP 配置前访问upload/index.php [root@test-a ~]# curl -x192.168.77.139:80 'www.test.com/upload/index.php'This is upload diretory 配置,/usr/local/apache2.4/......

野雪球
今天
3
0
java.util.Concurrent.Exchanger源码

类图 源码: package java.util.concurrent;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.AtomicReference;import java.util.concurrent......

狼王黄师傅
今天
6
0
Kubernetes里的secret最基本的用法

Secret解决了密码、token、密钥等敏感数据的配置问题,使用Secret可以避免把这些敏感数据以明文的形式暴露到镜像或者Pod Spec中。 Secret可以以Volume或者环境变量的方式使用。 使用如下命令...

JerryWang_SAP
昨天
5
0
2018-11-20学习笔记

1. python数据类型: 给变量赋值什么样的值,变量就是什么样的类型 给变量赋值整数,变量就是整数类型 给变量赋值字符串,变量就是字符串类型 123 和“123”一样吗? 在python中 单引号 与双...

laoba
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部