`
agapple
  • 浏览: 1595578 次
  • 性别: 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

我很疑惑

相关推荐

    JSON相关JAR包

    MyObject obj = new MyObject(); String json = gson.toJson(obj); MyObject newObj = gson.fromJson(json, MyObject.class); ``` 2. Jackson:由 FasterXML 维护的一个高性能的库,支持JSON处理的多种模式,包括...

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

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

    Activity传递对象的3种方法(全)

    MyObject obj = new MyObject(); Intent intent = new Intent(currentActivity, NextActivity.class); intent.putExtra("my_object", obj); startActivity(intent); // 在NextActivity中接收 MyObject receivedObj ...

    c#实现object与byte[]互转

    MyStruct obj = new MyStruct { Id = 1, Name = "test" }; // 序列化对象 using (MemoryStream stream = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream...

    JSON常用JAR包

    MyObject obj = new MyObject(); String jsonString = mapper.writeValueAsString(obj); MyObject newObj = mapper.readValue(jsonString, MyObject.class); ``` 2. Gson:Google开发的Gson库提供类似的转换功能。`...

    Refactor your Patter

    ComplexObject obj = new ComplexObject(); obj.name = name; return obj; } public static ComplexObject createWithNameAndAge(String name, int age) { ComplexObject obj = new ComplexObject(); obj....

    C#环境下线程安全的实现

    object obj = new object(); void SafeMethod() { lock (obj) { // 临界区代码 Console.WriteLine("Critical Section"); } } ``` 在这个例子中,通过 `lock` 语句自动实现了 `System.Monitor.Enter` 和 `...

    将对象保存到本地,保存到sd卡

    MyObject obj = new MyObject(); File sdCard = Environment.getExternalStorageDirectory(); File dir = new File(sdCard.getAbsolutePath() + "/MyAppFolder"); dir.mkdirs(); File file = new File(dir, "my...

    ArrayList的线程安全测试

    然而,ArrayList本身并不是线程安全的,这意味着在多线程环境中,多个线程同时访问和修改ArrayList时,可能会出现数据不一致或者竞态条件等问题。本测试着重于分析ArrayList在并发环境下的行为,并探讨如何确保其...

    ThreadLocked多线程共享资源

    object obj = new object(); lock (obj) { // 共享资源的代码 } ``` 这里,`obj`作为锁对象,确保同一时间只有一个线程能执行该代码块。 三、`Monitor`类 `Monitor`提供了更底层的线程同步功能,可以用于获取和...

    equals(Object) 与 == 的区别

    equals(Object) 方法的标准形式是 public boolean equals(Object obj),它返回一个布尔值,表示两个对象是否相等。在 Object 类中定义的 equals(Object) 方法只是简单地使用 == 运算符来比较两个对象的引用。因此,...

    NET中多线程间资源共享与访问

    object obj = new object(); lock (obj) { // 临界区内代码 } ``` - **Monitor类**:该类提供了更高级别的同步功能,包括`Enter()`和`Exit()`方法,用于手动管理锁定过程。 示例代码: ```csharp object ...

    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; } }

    java 实现下拉列表JComboBox中值和显示不同

    myObj obj = new myObj("单位 1", "danwei1"); vector.add(obj); obj = new myObj("单位 2", "danwei2"); vector.add(obj); obj = new myObj("单位 3", "danwei2"); vector.add(obj); obj = new myObj("单位 ...

    listview空间用法,序列化 ,二进制序列化

    MyObject obj = new MyObject() { Property1 = "Value1", Property2 = 2 }; using (Stream stream = File.Create("data.bin")) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream...

    Activity间的传值

    MyObject obj = new MyObject(); Intent intent = new Intent(this, TargetActivity.class); intent.putExtra("key", obj); startActivity(intent); ``` 接收端: ```java Intent intent = getIntent(); MyObject ...

    as和is的用法

    Object obj = new NewType(); NewType newValue = obj as NewType; ``` 在这个例子中,如果 `obj` 可以被转换为 `NewType` 类型,则 `newValue` 将被赋值为 `obj` 的转换结果;否则,`newValue` 将被设置为 `null`...

    java实现Object和Map之间的转换3种方式

    Object obj = beanClass.newInstance(); org.apache.commons.beanutils.BeanUtils.populate(obj, map); return obj; } public static Map, ?&gt; objectToMap(Object obj) { if (obj == null) { return null; } ...

    Javascript 面向对象 对象(Object)

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

Global site tag (gtag.js) - Google Analytics