`
agapple
  • 浏览: 1583311 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Object obj = new Object()引出的线程安全思考

    博客分类:
  • java
阅读更多

背景

上次在部门周会上抛出了一段代码:

 

Class SimpleCache {
   private Map cache = new HashMap() ; 
   public Object get(String key) {
      return cache.get(key);
   }

   public void reload(){
      Map tempCache = loadFromDB(); 
      cache = tempCache; // 位置1,引用切换
   }
}

是否是一个线程安全问题的操作。看似很简单的问题,其实发现自己也很难理的清楚,自己也是"道听涂说"的抛出了问题。

这里的关键点是在对应的位置1上,多线程中进行了一个引用切换,这是否是一个线程安全的操作??

 

因为jvm中的引用是基于双字节进行存储,会不会出现写了高位后,线程被换出,另一个线程读到了一个破损的地址导致程序出现异常?

过程

也没有绝对的标准,自己也试着去尝试分析一下这个问题。

 

首先看一下,jvm的内存模型:   http://kenwublog.com/explain-java-memory-model-in-detail

比较认可里面提到的几个内存模型的特征: 

 

  • Visibility 可视性
  • Ordering 有序性
这里也给出了对应jmm规范中,对应的工作模式: 


 

里面有对应的 main memory(主存)和work memory(工作内存)两类。

 

思考一下,java奉行的是"一处编译,到处运行“的理念,其实java是自己起了一个"操作系统",定义了自己的jmm模型,包括一些资源请求,多线程调度等。

所以jmm的定义,宏观上来说就是可以类比于操作系统的一些概念:

 

  • main memory  <=>  操作系统中的内存概念
  • work memory  <=>  cpu cache(L1,L2高速cache)
可以细细体会一下,是不是有点这么个意思?

继续往下看:
1. jmm中Ordering 有序性
操作系统中的多进程的资源进程,主要由mutex机制,P/V原语, semaphore信号量。
P(mutex); // 减少资源数
V(mutex); // 增加资源数

映射到jvm来看,其实都知道synchronized关键字,最终编译为class字节码后:
monitorenter
// do xxx
monitorexit

2. jmm中的Visibility 可视性
操作系统的cpu现在基本都已经步入多cpu,多核的时代,同时为了提升cpu的处理效率,引入了多级cache,比如L1,L2,L3。
用过cache的人都知道数据的一致性问题一直是一个头疼的问题,同样这个在cpu cache中同样的存在。 
看一下几篇文章:
linux下查看cpu cache : 
$ more /var/log/dmesg |grep cache
CPU: L1 I cache: 32K, L1 D cache: 32K
CPU: L2 cache: 256K
CPU: L3 cache: 8192K
解决缓存一致性的一般方法思路: 
a, 顺序一致性模型:
   要求某处理器对所改变的变量值立即进行传播, 并确保该值被所有处理器接受后, 才能继续执行其他指令.
b, 释放一致性模型:
   允许处理器将改变的变量值延迟到释放锁时才进行传播.

映射为jvm的多线程的work memory,所以同样会存在类似的问题,所以引入volatile关键字,可以用于解决数据可视性的问题。 
这里会引出jmm缓存一致性模型 – “happens-before ordering(先行发生排序)”,可以看一下: http://www.iteye.com/topic/260515

其他的一些内容
1. 指令重排:
这里有篇不错的文章,介绍的比较简单明了: http://kenwublog.com/illustrate-memory-reordering-in-cpu
为什么需要指令重排,就是cpu在一定的原则下,尽量加速cpu的执行速度,L1和L2cache的拓扑结构会决定其相关的一些行为。

2. 内存栅栏

正是因为有了指令重排的策略的存在,所以针对多线程编程,需要有一种barrier的策略。  http://www.infoq.com/articles/memory_barriers_jvm_concurrency

针对jmm定义中的volatile和synchronized做一些barrier指令的处理。就如那infoq文章中的描述: 

 

volatile在x86生成的指令:

 

0x03f83448: mfence                    ;...0faef0

 

synchronized生成的指令:

 

10  0x04d5edc0: lock cmpxchg %edi,(%esi)  ;...f00fb13e
...
18  0x04d5ede5: inc    %esi               ;...46
...
25  0x04d5edfd: lock cmpxchg %esi,(%edi)  ;...f00fb137

 

atomic compareAndSet()方法生成的指令:

 

14  0x02445220: lock cmpxchg %esi,(%edi)  ;...f00fb137

 

分析 

了解了前面的一些知识背景后,再回过来看一下最初的那段代码,是否存在线程安全问题?

答: 

  严格意义上来说这段代码会存在一些问题,因为没法保证cache的一致性问题,简单点的处理是增加一个volitile声明变量,保证线程更新时能更新cache指令。 (happens-before原则)

  java针对多字节的操作,java规范中有段描述: Java language Specification

 

When a thread uses the value of a variable, the value it obtains is in fact a value stored into the variable by that thread or by some other thread. 
This is true even if the program does not contain code for proper synchronization. 
For example, if two threads store references to different objects into the same reference value, the variable will subsequently contain a reference to one object or the other, 
not a reference to some other object or a corrupted reference value. (There is a special exception for long and double values; see §17.4.

17.4 Nonatomic Treatment of double and long

If a double or long variable is not declared volatile, then for the purposes of load, store, read, and write actions they are treated as if they were two variables of 32 bits each: 
wherever the rules require one of these actions, two such actions are performed, one for each 32-bit half. 
The manner in which the 64 bits of a double or long variable are encoded into two 32-bit quantities is implementation-dependent. 
The load, store, read, and write actions on volatile variables are atomic, even if the type of the variable is double or long.

 

  大意主要是说:java中除了long,double两种变量外,其他的变量类型针对多线程的赋值操作,不会出现写入一个字节的破损情况。  

 

stackoverflow.com的一个类似问题:http://stackoverflow.com/questions/1351223/thread-safe-setting-of-a-variable-java

 

感触

自己得多看看jls :  Java language Specification,可以加深自己对多线程的理解,同时也可以找到一些最权威的解惑,不会被他人的一些转载啥的文章给误导了

不过自己的e文能力需加强下,看的速度比较慢。对不起了老师,大学后基本没好好学英文

  • 大小: 38.7 KB
分享到:
评论
5 楼 kingquake21 2011-05-20  
agapple 写道
beneo 写道
kingquake21 写道
线程A在调用reload时修改的cache引用只是自己工作内存中的,并不能保证线程B看到这个修改

修改引用是一个原子操作,但是就像你后面提到的,没有happen-before的关系,所以仍然会存在多线程问题

如果改成volatile就OK了


并不能保证线程B看到这个修改
既然赋值是一个原子操作,那么看到没有看到又有什么关系呢?

或者说这个例子没有让我看到需要volatile的必要


恩,在这个场景下,不加volatile就是一个最终状态一致性的结果。可能两个线程会在两个不同的map中进行一个查找数据的过程,最终会达成一致状态。

如果你可以接受最终一致性,的确可以不需要volatile。 但比如使用一些变量做为标志位时,比如是否已经启动或者已经关闭等,对数据一致性有着比较高的要求,volatile就有其存在的必要性


给引用赋值是一个原子操作的含义只能保证引用地址的正确性,也就是说不会出现并发给long或double赋值时导致值的最终状态不一致。

但是,我想说的重点在下面

如果线程A给自己工作内存中的引用重新赋值,但是这个新的引用地址没有同步到共享内存中,那么线程B使用的引用就仍然是修改之前的,这样就导致两个线程访问的cache在某个时间段是不同的。
而使用volatile就会直接修改共享内存中的引用,所以可以解决这个问题
4 楼 agapple 2011-05-20  
beneo 写道
kingquake21 写道
线程A在调用reload时修改的cache引用只是自己工作内存中的,并不能保证线程B看到这个修改

修改引用是一个原子操作,但是就像你后面提到的,没有happen-before的关系,所以仍然会存在多线程问题

如果改成volatile就OK了


并不能保证线程B看到这个修改
既然赋值是一个原子操作,那么看到没有看到又有什么关系呢?

或者说这个例子没有让我看到需要volatile的必要


恩,在这个场景下,不加volatile就是一个最终状态一致性的结果。可能两个线程会在两个不同的map中进行一个查找数据的过程,最终会达成一致状态。

如果你可以接受最终一致性,的确可以不需要volatile。 但比如使用一些变量做为标志位时,比如是否已经启动或者已经关闭等,对数据一致性有着比较高的要求,volatile就有其存在的必要性
3 楼 beneo 2011-05-20  
kingquake21 写道
线程A在调用reload时修改的cache引用只是自己工作内存中的,并不能保证线程B看到这个修改

修改引用是一个原子操作,但是就像你后面提到的,没有happen-before的关系,所以仍然会存在多线程问题

如果改成volatile就OK了


并不能保证线程B看到这个修改
既然赋值是一个原子操作,那么看到没有看到又有什么关系呢?

或者说这个例子没有让我看到需要volatile的必要
2 楼 kingquake21 2011-05-20  
线程A在调用reload时修改的cache引用只是自己工作内存中的,并不能保证线程B看到这个修改

修改引用是一个原子操作,但是就像你后面提到的,没有happen-before的关系,所以仍然会存在多线程问题

如果改成volatile就OK了
1 楼 beneo 2011-05-19  
我说说我的想法

Class SimpleCache {
   private Map cache = new HashMap() ;&nbsp;
   public Object get(String key) {
      return cache.get(key);
   }

   public void reload(){
      Map tempCache = loadFromDB(); 
      cache = tempCache; // 位置1,引用切换
   }
}


java里面的assgin操作都是原子的,除了long,double,这个你也提到了,那么
Map tempCache = loadFromDB(); 肯定是原子的;cache = tempCache也肯定是原子的。既然都是原子的,那一致性指的是什么呢? reload多线程问题,也就只是不知道最后cache是哪个线程创建而已吧。。。。但是返回的结果还是正确的。

对比我想起了DCL,加volatile

我很疑惑

相关推荐

    c# object, dynamic, var的区别用例

    obj = new Program(); dynamic dyn; dyn = new Program(); //var v; v = new Program(); // 错误写法。正确写法如下: var v = new Program(); // 定义的同时必须指明初始化类型。 // 等价于:Program v = new ...

    Java JSON与Object互转源代码

    高效的JSON与Object互转的工具源代码,如果JSON数据未按...2. Object obj = JSONTool.convertJsonToObject(jsonStr); 缺点:对于集合(Collection,非Map)类数据结构,不能在集合中包含其他集合对象,但是可以包含数组。

    简单asp.net 的存储过程应用

    存储过程的应用+数据库 SqlParameter[] pr ={new SqlParameter("@name",SqlDbType.... object obj = com.ExecuteScalar(); if (obj != null) return Convert.ToInt32(obj.ToString()); else return 0; } }

    Javascript 面向对象 对象(Object)

    或者 var obj = new Object(); 为对象加入属性,方法: //=====第一种写法==================================== obj.name = ‘小明’; //为对象加属性 obj.updateName = function(name){//为对象定义updateName...

    根据数据库表自动产生javabean

    Object obj = clazz.newInstance(); Method ms[] = clazz.getMethods(); for (int i = 0; i ; i++) { String methodName = ms[i].getName(); if (methodName.startsWith("set")) { String ...

    Discuz!_X2笔记.pdf

    Discuz!_X2 笔记 单例模式(单一实例模式) class db { private static $obj = NULL; private function __construct() { ...self::$obj = new self(); } return self::$obj; } } ?&gt;....................

    一套比较完整的javascript面试题(部分答案)

    原因:var obj = new Array ();是对的;JavaScript 中大括号表示创建对象。var obj = { id:1, name:”jacky” };alert&#40;obj.name&#41;;上例表示创建一个具有属性 id (值为 1)、属性 name(值为 jacky)的对象...

    Java基于双向链表实现双端队列结构(算法源码)

    DLNode first = new DLNode(obj, header, second); second.setPrev(first); header.setNext(first); size++; } //在队列后端插入新节点 public void insertLast(Object obj) { DLNode second = ...

    基于双向链表实现双端队列结构算法(java算法源码)

    DLNode first = new DLNode(obj, header, second); second.setPrev(first); header.setNext(first); size++; } //在队列后端插入新节点 public void insertLast(Object obj) { DLNode second = ...

    学生选课全代码sqL

    后台实现代码如下: ... { string sqlString = ... object obj = SqlHelper.ExecuteScalar(SqlString,CommandType.Text, sqlString); if(obj!=null && obj.ToString()!="0") return true; else return false; }

    adi-recap:ESI - ADI 回顾练习

    Object obj = new Object ();String json = gson . toJson(obj); // Serialize obj to json, works with a collectionObject obj2 = gson . fromJson(json); // deserializes json into object库 JSON-simple(检查 ...

    又小又快又无依赖Json序列化与反序列化

    Object obj=JsonUtil.instance().json2Obj(json,Object.class) Object[]objs=JsonUtil.instance().json2Obj(json,Object[].class); //泛型 DatagridReturn&lt;Object&gt; dgr=JsonUtil.instance().json2Obj(json...

    apache-cxf-3.0.16-jar.zip

    Object[] obj = new Object[2]; obj[0]="CxfWebservice"; obj[1]="192.168.0.163"; Object[] response = client.invoke("sayHi",obj); System.out.println("调用CxfWebservice结果 : " + response[0]); }

    CMS.DBUtility.dll

    if ((Object.Equals(obj, null)) || (Object.Equals(obj, System.DBNull.Value))) { cmdresult = 0; } else { cmdresult = int.Parse(obj.ToString()); } if (cmdresult == 0) { return false; } else ...

    3D_obj模型

    可以用three.js加载的Obj模型,可以用photoshop打开下。

    gson 使用Gson将Java对象转换为JSON

    TestObjectToJson obj = new TestObjectToJson(); Gson gson = new Gson(); String json = gson.toJson(obj); 会输出 {"data1":100,"data2":"hello"} 2. fromJson() example Java 代码 收藏代码 1. import ...

    js如何获取object类型里的键值

    最近遇到一个问题: 代码如下: var obj = {“name1″:”张三”,”name2″:”李四”};... 于是,我想到了js中遍历对象属性的方法: 代码如下: function printObject(obj){ //obj = {“cid”:”C0″,”ctex

    Socket聊天室源程序

    JMenuItem connectItem=new JMenuItem ("连接设置(C)"); JMenu helpMenu=new JMenu ("帮助(H)"); JMenuItem helpItem=new JMenuItem ("帮助(H)"); //建立工具栏 JToolBar toolBar = new JToolBar(); //...

    struts2中范围对象的操作

    Object obj=actionContext.get("objName");----Object obj=request.getAttribute("objName"); actionContext.put("objName",objName);---request.setAttribute("objName"); &lt;s:property value="#request['objName']...

    java动态代理和反射

    private Object obj; public HireProxy(Object obj) { super(); this.obj = obj; } //第二个参数method,被代理对象的接口方法 @Override public Object invoke(Object proxy, Method method, Object[] ...

Global site tag (gtag.js) - Google Analytics