- 浏览: 216908 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
zhangwei412827_:
[flash=200,200][/flash]
js 关键字 in 的使用方法 -
flycatdeng:
.classpath文件在哪里?
AndEngine示例运行环境搭建过程 -
revol:
请问,你是如何调用LSMessage,并弹出窗口的?
Silverlight模拟MSN和QQ即时提示消息框
多线程编程从来都是一件比较困难的事情,调试多线程程序也相当困难,这种困难来自于线程对共享资源操作的复杂性 ( 包括对于资源操作的线程间的先后顺序 ) 。对于 Java 来说,它封装了底层硬件和操作系统之间很多的细节,对于线程之间的调度底层细节我们大多数时候不用关心,然而真正编写 java 多线程程序时有一些东西我们却是不得不知道的。
在 java 中,
1、 多个线程之间数据交换是依靠内存来实现的。
2、 缓存:为了获得较高的性能,处理器读取内存中的数据后,可能会存储在自身缓存中,计算得到的新的结果值也可能直接写到自身缓存中,等待合适的时机再刷新到内存中去,在数据刷新到内存中之前,别的处理器是看不到这个更新的值的。 ( 这个造成的问题可能是缓存与主存中的数据不一致,这也就引出了值的可见性的问题 )
3、 次序:同样,为了获得最优性能, java 允许编译器在不修改程序语义的前提下,可以随意的排序某些指令的执行顺序,甚至允许处理器以颠倒的次序执行一些操作,例如,允许缓存以程序写入变量时不同的顺序把变量刷新到主存中。
注意以上提到的几点,理解他们在 java 多线程编程中至关重要,再继续之前,我来举两个实际的例子来说明一下上面的 2 和 3 点。
对于第 2 点,先来 看这一段代码:
- (1)y=5;
- (2)b=y+3;
在单线程中执行这一段代码,处理器将 y 读入缓存,并且在执行 y+3 后,将计算到的结果再次存入缓存中,在某个时候 ( 可能立即,也可能在之后的某个合适的时间 ) 再将这个 b 值刷新到内存中。
换成多线程,线程 A 执行 (1)(2) ,而从另外一个线程 B 来读取 b 的值会有什么样的结果呢?答案是不确定。为什么呢 ? 即使是 A 线程先执行了 (1)(2) ,线程 B 再读取 b 的值,也有可能读取不到,因为有可能 b 的真正的值可能还在缓存中,而线程 B 只能从自己的缓存或者从内存中去读取所要的值,这就会造成 B 读到的可能是一个过期的内存值。
对于第 3 点,排序是什么意思呢?同样来看一段代码
- (3) a=5
- (4)b=6
- (5)c=7
- (6)d=8
- (6)e=a+b
- (7)f=c+d
我们直观会觉得,处理器会依次执行上面的代码,但是答案也是不确定的,因为不同的编译器或处理器为了获得最高性能,很可能会调整最终代码的执行顺序,只要最终不影响程序语义即可,例如,这里可以先执行 (3)(4)(6), 再执行 (5)(6)(7) ,这都是不影响最终结果和语义的,怎么调整都可以。更甚至对于这样的顺序操作 A. 从内存读取数值到缓存 B. 执行得到结果并放入缓存 C. 将缓存数据刷新到内存,这么几步操作都有可能被编译器给颠倒执行,本来正常应该 ABC 的顺序,最后真正执行的可能是 ACB 的顺序。
对于只在线程内执行的操作和访问的变量来讲,上面的几点都不会有问题,而对于会在多线程中来访问和操作的变量来说上面的优化可能会变成了灾难。
为了解决资源争用的问题, java 引入了 synchornized 关键字,同时它还有另外一层语义,那就是解决了值可见性问题。
Synchornized 关键字保证了:
1 、 在进入同步块时,失效缓存,强制从内存读取最新值。
2、 在退出同步块时,将缓存值强制刷到内存中。
以上两点保证了同一个监视器保护下的多个线程都可以看到最新值,而不会读取到过期值,如果线程 A 进入同步块,执行后得到的所有共享变量值,在它退出后,对紧接着进入同一个同步块的线程 B 都是可见的,即线程 B 可以保证读取到线程 A 在同步块中计算到的共享变量的最新值。
在 java 中还定义另一个关键字,也一样可以保证变量的在跨线程中的可见性,那就是 volatile ,它保证读写直接在主存而不是寄存器或者本地处理器缓存中进行。即使用 volatile 修改的变量可以保证在其他线程中读取该变量时可以读取到,例如,如果上面 (2) 中 b 加上 volatile 关键字,那么在线程 B 中就可以立马看到该变量修改的最新值。
如果既没有使用 synchornized ,也没有使用 volatile 的共享资源,那么在 java 中是不保证线程之间对最新值是可见的。
上面还谈到了编译器对内存操作重排序的问题,这有什么影响呢? 看如下代码,
- char [] config;
- (8)boolean initialized = false ;
- // In Thread A
- (9)config = readConfigFile(fileName);
- (10)initialized = true ;
- // In Thread B
- while (!initialized)
- sleep();
- // use config
原始想法是线程A 如果完成了对config 的初始化,设置initialized 为true 表示初始化完成,B 线程如果检测到初始化完成,则执行use config 。然而这段代码可能并不会像我们想的那样运行,前面说过了,有可能线程B 永远都看不到initialized=true 的那一天,因为这里没有任何保证线程B 能够看到initialized读取到最新值, 如果initialized 加上volatile 关键字会怎么样呢?将(8) 修改成 volatile boolean initialized = false; 就可以保证线程B 可以看到initialized 的最新值。在JDK1.5 之前,解决了这个可见性问题,但是又有一个问题出现了,因为JDK1.5 之前 编译器对 volatile 变量的读和写不能与对其他 volatile 变量的读和写一起重新排序,但是它们仍然可以与对不是 volatile 变量的读写一起重新排序,意思是说,这里 (9) 和 (10) 的执行顺序有可能被编译器给颠倒了,此时如果线程 B 检测到 initialized 为true ,准备执行config 时,却因为config 没有被初始化导致代码出现严重错误,杯具。可见,这里编译器调换了执行顺序对于多线程来说有时候是多么可怕。但是在JDK1.5 中, volatile 又增加了一个语义,那就是申明了 volatile 的变量告诉编译器不能和其他非 volatile 变量一起排序,同时 volatile 变量自身的所有内存操作也必须按照顺序执行,不能颠倒。因此 volatile 的变量其实是关闭了编译器对其的优化。
前面也讲了,在线程内编译器对操作进行排序优化,只要其中不要涉及到公共资源的操作,并不会引起什么问题,但是一旦进行了排序,而我们在大多数时候又无法预料线程与线程之间的操作执行顺序,就可能会引起程序 crash.
在 java 中,新的 java 内存模型定义了一部分线程与线程之间操作的执行顺序,叫做 happen-before ,它保证只要满足 happen-before 关系,那么后面的操作可以看到前面操作的结果。
- 线程内的每个操作happen-before 稍后按程序顺序传入的该线程中的每个操作。
-
一个解除锁监视器的(synchronized
阻塞或方法退出)happen-before
相同监视器的每个后续锁(synchronized
阻塞或方法进入)。并且因为 happen-before 关系是可传递的,所以解除锁定之前的线程的所有操作 happen-before 锁定该监视器的任何线程后续的所有操作。 - 写入volatile 字段happen-before 每个后续读取相同字段。volatile 字段的读取和写入与进入和退出监视器具有相似的内存一致性效果,但不需要互斥锁。
- 在线程上调用start() happen-before 在启动的线程中的所有操作。
- 线程中的所有操作 happen-before 从该线程上的 join 成功返回的任何其他线程。
当有一个变量被多个线程读、被至少一个线程写、并且读和写不是按 hanppens-before 关系排序的时,程序就称为有 数据争用 ,因而不是一个 “ 正确同步 ” 的程序。
明白了以上几点,就可以解释一个经典 DCL 问题,例如:
- public class Singleton {
- private static Singleton instance= null ;
- public static Singleton getInstance()
- {
- if (instance == null )
- {
- synchronized(Singleton.class ) { //1
- if (instance == null ) //2
- instance = new Singleton(); //3
- }
- }
- return instance;
- }
- }
这段代码有问题么?标准的double check. instance = new Singleton() 根据以上几点分析, 可能执行执行了下列伪代码:
-
mem = allocate();
//Allocate memory for Singleton object.
-
instance = mem; //Note that instance is now non-null, but
-
//has not been initialized.
-
ctorSingleton(instance); //Invoke constructor for Singleton passing
-
//instance.
注意,当线程
A
执行到instance = mem
时,线程B
正好执行到外部的instance == null
,
此时,这个引用已经
不为null
,但是这个statnce
还没有构造完成,线程B
的操作立即返回使用该instance,
这是不安全的。这是从操作次序被重新排序得到的分析结果,从另外happen-before
的角度来看,这里多个线程操作共享变量instance
之间并没有明显的happen-before
关系,因此多个线程对instanc
的读写可能发生不可见的情况。instance
变量申明为volatile
即可,既保证了可见性,又保证了操作不会被排序。然而,使用volatile
来实现毕竟有性能损耗,因此如果要实现单例,完全可以避免使用DCL
,而采用static
方式。例如:
要解决上面提到的问题,将该
- public class Singleton {
- private static class Singleton Holder{
- private static Singleton instance = new Singleton ();
- }
- public static Singleton getInstance(){
- return SingletonHolder.instance ;
- }
- }
这种实现方式既保证了足够的惰性,又避免了同步或者保持可见性带来的性能损耗。
发表评论
-
实现java bean的懒加载
2016-09-29 13:25 2039hibernate的lazy懒加载: ... -
如何用Maven创建web项目(具体步骤)
2016-08-22 12:22 681使用eclipse插件创建一个web project ... -
JVM参数测试情况
2016-07-05 10:58 760JVM包括如下核心组 ... -
国内maven仓库
2016-06-27 09:20 1835记录下,国内好用的maven仓库,oschina提供的:ht ... -
各种 Comet 技术优缺点对比
2015-12-15 09:16 488script tag iframe a ... -
Web 通信 之 长连接、长轮询(long polling)
2015-12-15 09:09 975Web 通信 之 长连接、长轮询(long polling ... -
计算本周第一天
2014-01-13 09:38 630网上有很多计算本周第一天的代码,但是大多数都不太全面。 ... -
Java中的transient,volatile和strictfp关键字
2013-09-01 09:57 8031、transient 如果用transien ... -
设计模式之策略模式
2013-08-25 22:05 777策略(Strategy)模式: 策略模式的用 ... -
设计模式之工厂模式(2)
2013-08-21 22:30 7641、定义 多个工厂模式:工厂类中定义多个方法, ... -
设计模式之简单工厂模式
2013-08-21 21:33 8901、定义 简单工厂模式:一个创建产品对象 ... -
Class.isAssignableFrom(Class clz)方法 与 instanceof 关键字的区别
2013-08-19 22:53 854Class.isAssignableFrom()是用来判断一 ... -
Java多线程之教程
2013-08-18 22:11 815《Java并发编程实践》是非常详细阐述了Java多线程开发 ... -
Mina 之 入门篇(1)
2013-08-17 23:36 3957最近自己在工作之余做一个金融类的项目(类似股票),采用min ... -
Mina 之 Mina的异步机制
2013-08-17 21:05 3473将mina异步机制之前,先了解下同步和异步的区别: (1 ... -
图文教程vs2008+eclipse开始jni教程实例(一)
2013-08-06 09:52 10401、在eclipse中新建一个testJni的工程,新建一个 ... -
浅析 Java Thread.join()
2013-07-29 09:26 1016一、在研究join的用法之前,先明确两件事情。 1.jo ... -
正确理解ThreadLocal
2012-11-26 13:59 640首先,ThreadLocal 不是用来解决共享对象的多线程访问 ... -
HashMap和ConcurrentHashMap的对比
2012-11-26 13:19 1772(1) 简单 put ... -
Volatile与sychronized
2012-11-26 13:16 1096在使用 volatile 关键字时要慎 ...
相关推荐
高级java笔试题 《Java 并发编程实战》阅读笔记 ...因为并发、并行本身,是有悖于我们大脑的工作模式的,也就是说,我们长期的写码不得不 而一旦将这一个一个的线程组合起来,奇妙的 bug 发生了...
面试必考之HashMap源码分析与实现 ,微服务架构之Spring Cloud Eureka 场景分析与实战,高性能必学之Mysql主从架构实践 ,架构师不得不知道的Spring事物不能回滚的深层次原因 ,分库分表之后分布式下如何保证ID全局...
│ │ 9.JAVA并发编程之多线程并发同步业务场景与解决方案.wmv │ │ │ ├─10.微服务架构之Spring Cloud Eureka 场景分析与实战 │ │ 10.微服务架构之Spring Cloud Eureka 场景分析与实战.wmv │ │ │ ├─11....
JAVA 7 程序设计.part1.rar(解压需2个文件part1,part2目前一次只能上传70M,不得不分卷,希望大家谅解下) 本书由全球资深Java技术专家、高级Java企业级应用架构师、《How Tomcat Works》作者亲自执笔,权威性...
JAVA 7 程序设计.part2.rar(解压需2个文件part1,part2目前一次只能上传70M,不得不分卷,希望大家谅解下) 本书由全球资深Java技术专家、高级Java企业级应用架构师、《How Tomcat Works》作者亲自执笔,权威性...
JAVA并发编程之多线程并发同步业务场景与解决方案 锁、分布式锁、无锁实战全局性ID 微服务架构之Spring Cloud Eureka 场景分析与实战 高性能必学之Mysql主从架构实践 架构师不得不知道的Spring事物不能回滚的深...
A: 由于下载人数众多,下载服务器做了并发的限制。若发现下载不了,请稍后再试,多次下载是不会重复扣分的. Q: 我的积分不多了,如何获取积分? A: 上传优质资源可以获取积分,详细见积分规则。 下载本文件意味着您...
我就是当时没有打好基础,最后学习Java web、Java框架的时候对这些东西很难有深刻的理解,所以我不得不回头来恶补基础。今天我们就来谈谈并发那些事。 并发 并发就是将程序划分为多个任务,每个任务可以在同一时间内...
leetcode卡 取法其上,得乎其中,取法其中,得乎其下,取法其下,法不得也 做了4年的业务开发,由于种种原因,发现自己的技术理想越来越遥远,现在要利用一切方法,在人生的马拉松中跑出好成绩,每天坚持...并发编程
以至于正当Microsoft尽力在Visual J++基础上拓展Java功能,并使之与Windows操作系统紧密结合在一起的时候,Sun公司对Microsoft提出了法律诉讼,控告Microsoft违反了许可证协议中的条款,最终的结果是Microsoft公司...
FAQ为什么我点的下载下不了,但积分却被扣了由于下载人数众多,下载服务器做了并发的限制。若发现下载不了,请稍后再试,多次下载是不会重复扣分的。我的积分不多了,如何获取积分?上传优质资源可以获取积分,详细见 ...
A: 由于下载人数众多,下载服务器做了并发的限制。若发现下载不了,请稍后再试,多次下载是不会重复扣分的. Q: 我的积分不多了,如何获取积分? A: 上传优质资源可以获取积分,详细见积分规则。 下载本文件意味着您...
FAQ为什么我点的下载下不了,但积分却被扣了由于下载人数众多,下载服务器做了并发的限制。若发现下载不了,请稍后再试,多次下载是不会重复扣分的。我的积分不多了,如何获取积分?上传优质资源可以获取积分,详细见 ...
为了安全,要对对每个目录按不同的用户设置不同的访问权限,然后关闭一些不需要的服务,这样可以对不良人士利用IIS溢出漏洞访问到系统盘作个第一级防护。 3.iis安全设置之端口设置。IIS有默认的端口设置,只要稍有...