解惑如何保证数组元素的可见性

本文探讨了ConcurrentHashMap在Java 1.8中的实现细节,特别是如何利用volatile关键字及Unsafe类来确保线程间数组元素的可见性。揭示了即使数组本身被标记为volatile,其元素也需要额外机制来保证一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://mp.weixin.qq.com/s?__biz=MzIwMzY1OTU1NQ==&mid=2247484057&idx=1&sn=c5fb63c68203001fa3a0545cef6c0e06&chksm=96cd42d5a1bacbc325759048d6149d49f8c918d8201026fbb1388e1b8f5247d5a147a9e04a24&mpshare=1&scene=1&srcid=0320dLWL3ZptBnHWOIppB46B&key=e372e0f925d47d533edbae8bbd468b84e65f35e0b04cc9bdb6c760a92c8ee6ca4456a735b9106dc2c33ebd40cbce69cf1def258ecd75affb0b7f0783470b16655f19306069e992fab4039316dfeecab8&ascene=0&uin=MTA2NzUxMDAyNQ%3D%3D&devicetype=iMac+MacBookAir6%2C2+OSX+OSX+10.10.5+build(14F2511)&version=11020012&lang=zh_CN&pass_ticket=YOhvdyFJYno3pJPXiaSEWxhPrymv49PNwcVMB6NeX3FSIg%2FYjhsf%2FN9wmUt57lX5




这篇文章时隔一两年,突然看到还是有点印象,文章中,我只是强硬的抛出了一个结论:虽然table变量被volatile修饰了,但里面的元素并没有volatile修饰,无法保证元素的可见性。

但是这种解释,似乎被众多的小伙伴怀疑,也一直没有找到有理的证据(基础还是太弱)

不过在星球中,大家还是很积极的讨论,虽然有些是错的(这种问题在大部分人身上都有,你不表现出来,那你永远不知道自己所掌握的东西其实是不对的)




解惑

在ConcurrentHashMap(1.8)中,内部使用一个volatile的数组table保存数据,细心的同学可以发现,Doug Lea每次在获取数组的元素时,采用Unsafe类的getObjectVolatile方法,在设置数组元素时,采用compareAndSwapObject方法,而不是直接通过下标去操作,这是为什么?

今天得到R大的确认:这个是因为Java数组在元素层面的元数据设计上的缺失,无法表达元素是final、volatile等语义,所以开了后门,使用getObjectVolatile用来补上无法表达元素是volatile的坑,@Stable用来补上final的坑,数组元素就跟没有标volatile的成员字段一样,无法保证线程之间可见性。

只有触发happens before关系的操作,才能保证线程之间的可见性,比如使用table[0] = new Object()直接赋值,这个赋值不会触发任何happens before关系的操作,相当于对一个无volatile变量进行赋值一样。


========================

Get实现  java.util.concurrent.ConcurrentHashMap.get(Object)

 

 

Java代码  收藏代码
  1. public V get(Object key) {  
  2.         Segment<K,V> s; // manually integrate access methods to reduce overhead  
  3.         HashEntry<K,V>[] tab;  
  4.         int h = hash(key.hashCode());  
  5.         long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;  
  6.         if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&  
  7.             (tab = s.table) != null) {  
  8.             for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile  
  9.                      (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);  
  10.                  e != null; e = e.next) {  
  11.                 K k;  
  12.                 if ((k = e.key) == key || (e.hash == h && key.equals(k)))  
  13.                     return e.value;  
  14.             }  
  15.         }  
  16.         return null;  
  17.     }  

 

get没有使用锁同步,而是使用轻量级同步volatile原语sun.misc.Unsafe.getObjectVolatile(Object, long),保证读到的是最新的对象。

出自:http://hill007299.iteye.com/blog/1490779


### C语言解惑:指针数组 #### 指针数组的概念 指针数组是一个非常重要的概念,在C语言中广泛应用。它本质上是指向其他变量地址的元素构成的一个数组。每个数组元素本身都是一个指针。 例如,如果你有一个字符指针组成的数组 `char *array[5];` 这意味着你可以将这个数组看作是可以存储最多五个字符串(通过它们各自的首字母地址表示)。换句话说,指针数组允许你动态地管理一组数据结构,并且可以简化对大型复杂程序中的资源处理过程。 下面给出一段简单的示例代码来帮助理解: ```c #include <stdio.h> #include <string.h> int main() { // 定义指向整数类型的指针数组并初始化 int numbers[] = {10, 20, 30}; int* ptrArray[3]; for (int i=0; i<3; ++i) { ptrArray[i] = &numbers[i]; // 将数字对应的内存地址赋值给ptrArray数组相应位置上保存着的“指向整型”的指针 } printf("Original values:\n"); for(int j=0;j<3;++j){ printf("%d\n", *ptrArray[j]); /* 解引用操作符'*'用于访问由某特定指针对应的实际内容 */ } return 0; } ``` 上述例子展示了如何声明、分配以及遍历包含整数类型的数据集。当然,这里只是基础用法;实际上还可以创建更复杂的组合形式如双重间接寻址等高级技巧的应用场景也非常广泛。 对于字符序列来说同样如此。我们可以定义一个指向字符串常量表项(即以'\0'结尾的一系列连续字符)的指针数组: ```c const char *fruits[] = {"apple", "banana", "cherry"}; for(size_t k=0;k<sizeof(fruits)/sizeof(*fruits);++k) { puts(fruits[k]); } ``` 这段代码片段实现了依次打印出三个水果名称的功能。其中`sizeof()`函数是用来计算大小的工具,它可以用来获取整个数组占用字节数除以单个成员所占空间得到总数目。 希望这能帮到您理解C语言里的指针数组
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值