java对象可变状态风险
来源:互联网 发布:灰鸽子源码 vc版 编辑:程序博客网 时间:2024/06/05 02:46
*****摘自《七周七并发编程》《深入理解java虚拟机》
多线程下变量修改的可见性
现代CPU一般都使用读写速度很快的高速缓存来作为内存和cpu之间的缓冲,高速缓存的引入可以有效解决CPU和内存之间的速度矛盾。但同时引入了新的问题:缓存一致性。多CPU系统,每个处理器都有自己的高速缓存cache,而高速缓存共享相同内存,为了解决缓存一致性问题,需要各个处理器访问缓存时遵循一定的协议。
此外,为了获取好的执行效率,处理器可能会对代码进行乱序执行优化,处理器会再计算之后将乱序执行的结果进行重组,保证该结果与顺序执行的结果一致。但并不保证程序中各个语句执行的顺序与代码的顺序一致。
java虚拟机中,即时编译器也会进行指令重排序优化,是在机器层面的优化,执行的线程是无法感知这种优化的。
java内存模型规定了所有的变量都存储在主内存中, 除此之外每个线程都有自己的工作内存, 线程的工作内存中保存了被该线程使用到的变量的副本拷贝, 线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行, 而不能直接读写主内存中的变量. 不同的线程之间也无法直接访问对方工作内存中的变量, 线程间变量值的传递均需要通过主内存来完成.
由上可知, 一个线程修改了变量的值, 另一个线程并非总是能够及时获知最新的值, 这就是可见性问题的根源。
隐藏的可变状态
class DataParser{private final DataFormat format = new SimpleDateFormat("yyyy-MM-dd");public Date parse(String s) throws ParseException{return format.parse(s)}}多个线程使用这个类的同一对象时,首次运行可能得到如下错误:java.lang.NumberFormatException:for input string :".12012E4.12012E4"
再次运行,可能会得到如下错误:caught:java.lang.ArrayIndexOutOfBoundsException:-12012E4
第三次运行,可能还会得到另一个错误:java.lang.NumberFormatException:multiple points虽然这段代码只有一个成员变量,且被标记为不可变(即final),但显然这段代码根本达不到线程安全,为什么呢?造成的原因是SimpleDateFormat内部有隐藏的可变状态,你可能会认为这应该是个bug,但对我们来说是不是bug并不重要,Java这类语言为了让代码写起来简单,在此隐藏了可变状态,也是我们无法判断何时会发生错误——从API无法判断SimpleDateFormat是否是线程安全的。隐藏的可变状态还不是唯一需要留意的问题,我们再来看一个。
逃逸的可变状态假设我们要创建一个管理比赛的web服务,需求是能管理一个运动员列表,我们会习惯的写如下代码:
public class Tournament{private List<Player> players = new LinkedList<Player>();public synchronized void addPlayer(Player p ){players.add(p);}public synchronized Iterator<Player> getPlayerIterator(){return players.iterator();}}通常我们会认为这段代码是线程安全的——players是私有变量,仅addPlayer()和getplayerIterator()使用,且两个方法都标记了synchronized,然而它并不是线程安全的,因为getPlayerIterator()返回的迭代器仍引用了players内部的可变状态——如果迭代器在使用时,另一个线程调用了addPlayer()方法,那么程序就会抛出ConcurrentModificationException或变得更糟,也就是说可变状态从Tournament在重重防护下逃逸了。
在并发程序中,隐藏和逃逸仅仅是两种可变状态带来的风险——还有件很多其他的风险。
this逃逸
指构造函数返回之前其它线程就持有该对象的引用,调用尚未构造完全的对象的方法可能引起错误。this逃逸经常发生在构造函数中启动线程或注册监听器时, 如:
public class ThisEscape { public ThisEscape() { new Thread(new EscapeRunnable()).start(); // ... } private class EscapeRunnable implements Runnable { @Override public void run() { // 通过ThisEscape.this就可以引用外围类对象, 但是此时外围类对象可能还没有构造完成, 即发生了外围类的this引用的逃逸 } } }
逃逸分析
逃逸分析,是目前Java虚拟机中比较前沿的优化技术,它与类型继承关系分析一样,并不是直接优化代码的手段,而是为其它优化技术提供依据的分析技术。逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传到其它方法中,称为方法逃逸。甚至还有可能被其它线程访问到,比如赋值给类变量或可以在其它线程中访问的实例变量,称为线程逃逸。如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可能为这个变量进行一些高效的优化。(1)栈上分配;(2)同步消除;(3)标量替换。
- java对象可变状态风险
- JAVA 可变对象,不可变对象
- java可变对象的序列化问题
- java中可变对象与不可变对象
- JAVA线程对象的状态
- hibernate中的java对象状态
- Java对象的线程状态
- 可变java对象存入hashSet引发的问题
- Java中关于返回引用可变对象常见问题剖析
- Java门派的风险
- 内部可变状态escape实例
- Hibernate中的JAVA对象有三种状态
- Hibernate中java对象的状态
- hibernate中java对象的状态
- 74 Java 对象在内存中状态
- Hibernate应用中Java对象的状态
- Hibernate中java对象的状态
- Hibernate中java对象的状态
- U-Boot移植--开机声音
- 九个 Console 命令,让 js 调试更简单
- IO流 复制示例
- 《Master Opencv...读书笔记》非刚性人脸跟踪 III
- 数据库范式——通俗易懂【转】
- java对象可变状态风险
- Solr4.3.1配置MMSeg4j1.9.1中文分词器
- [插件分享]Fluvio Pro3.0 unity流体物理插件
- Java之美[从菜鸟到高手演变]之常见的几种排序算法-插入、选择、冒泡、快排、堆排等
- 《Master Opencv...读书笔记》非刚性人脸跟踪 IV (终)
- 实现一个函数PrintN,使得传入一个正整数为N的参数后,能顺序打印从1到N的全部正整数.
- CodeForces 660ACo-prime Array(GCD+思维)
- 利用 Arrays.sort 字符串 进行排序 完全按字符 排序 忽略字符大小写
- sbt下载jar包速度慢