Object obj = new Object()引出的线程安全思考-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

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

简介:

背景

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


1.Class SimpleCache {  
2.   private Map cache = new HashMap() ;   
3.   public Object get(String key) {  
4.      return cache.get(key);  
5.   }  
6.  
7.   public void reload(){  
8.      Map tempCache = loadFromDB();   
9.      cache = tempCache; // 位置1,引用切换  
10.   }  
11.}  

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

这里的关键点是在对应的位置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信号量。


1.P(mutex); // 减少资源数  
2.V(mutex); // 增加资源数  

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


1.monitorenter  
2.// do xxx  
3.monitorexit  

2. jmm中的Visibility 可视性
操作系统的cpu现在基本都已经步入多cpu,多核的时代,同时为了提升cpu的处理效率,引入了多级cache,比如L1,L2,L3。
用过cache的人都知道数据的一致性问题一直是一个头疼的问题,同样这个在cpu cache中同样的存在。 
看一下几篇文章:
linux下查看cpu cache : 

1.$ more /var/log/dmesg |grep cache  
2.CPU: L1 I cache: 32K, L1 D cache: 32K  
3.CPU: L2 cache: 256K  
4.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生成的指令:


1.0x03f83448: mfence                    ;...0faef0  

synchronized生成的指令:


1.10  0x04d5edc0: lock cmpxchg %edi,(%esi)  ;...f00fb13e  
2....  
3.18  0x04d5ede5: inc    %esi               ;...46  
4....  
5.25  0x04d5edfd: lock cmpxchg %esi,(%edi)  ;...f00fb137  

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


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

分析 

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

答: 

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

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


1.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.   
2.This is true even if the program does not contain code for proper synchronization.   
3.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,   
4.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.  


1.17.4 Nonatomic Treatment of double and long  
2.  
3.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:   
4.wherever the rules require one of these actions, two such actions are performed, one for each 32-bit half.   
5.The manner in which the 64 bits of a double or long variable are encoded into two 32-bit quantities is implementation-dependent.   
6.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文能力需加强下,看的速度比较慢。对不起了老师,大学后基本没好好学英文

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章