java中CAS实现原理

java中在Unsafe中提供了三种CAS操作:

compareAndSwapInt(),compareAndSwapObject(),compareAndSwapLong()

//参数含义:对象,属性的内存偏移量,属性期望值,属性更新值
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

偏移量:内存中的对象包含对象头和对象实例数据,而对象头占8个字节,对于64位操作系统的压缩指针占4个字节, 所以我们一般说对象头占12个字节;例如对于一个test对象,x偏移量为这个对象的对象头即12个字节,y的偏移量为16

cas操作修改Test类的x变量。

public class CASTest {

    public static void main(String[] args) {
        Test test = new Test();
        Unsafe unsafe = UnsafeFactory.getUnsafe();
        long xOffset = UnsafeFactory.getFieldOffset(unsafe, Test.class, "x");
        System.out.println(xOffset); //12
        long yOffset = UnsafeFactory.getFieldOffset(unsafe, Test.class, "y");
        System.out.println(yOffset); //16
        unsafe.compareAndSwapInt(test, xOffset, 0, 1);
        System.out.println(test.x);
    }
    
    static class Test {
        int x;
        int y;
    }
}

CAS可以保证原子性,但是不能保证有序性和可见性,所以一般地,CAS配合volatile使用可以保证线程安全。底层最终通过执行一个cas指令(原子操作修改变量值),通过期望值和内存中的实际值进行比较,比较的结果如果相等则返回的是旧值(期望值)表示CAS操作成功,不相等则返回内存中实际值表示CAS操作失败。

CAS实现线程安全的操作

public class CASTest {

    private static int sum = 0;
    private static CASLock casLock = new CASLock();

    public static void main(String[] args) throws InterruptedException {
        for (int i=0; i<10; i++) {
            new Thread(() -> {
                for (;;) {
                    if (casLock.getState() == 0 && casLock.cas()) {
                        try {
                            for (int j = 0; j < 10000; j++) {
                                sum++;
                            }
                        } finally {
                            casLock.setState(0);
                        }
                        break;
                    }
                }
            }).start();
        }
        Thread.sleep(2000);
        System.out.println(sum);
    }
}
public class CASLock {

    private volatile int state = 0;
    private static final Unsafe UNSAFE;
    private static final long OFFSET;
    static {
        UNSAFE = UnsafeFactory.getUnsafe();
        OFFSET = UnsafeFactory.getFieldOffset(UNSAFE, CASLock.class, "state");
    }
    
    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public boolean cas() {
        return UNSAFE.compareAndSwapInt(this, OFFSET, 0, 1);
    }
}

jdk中juc包下原子类都是通过cas实现线程安全的。

ABA的问题可以通过版本号来解决

LongAdder、DoubleAdder原理

在高并发下,CAS操作会有大量的线程自旋,导致浪费线程资源,而为了提高执行效率,将V值拆分为多个变量,多个线程同时对各自的变量进行cas操作,所有线程执行完成后,将所有变量进行累加统计。其思想与jdk8中ConcurrentHashMap统计元素个数类似,LongAdder、DoubleAdder也实现了该思想。LongAdder中定义了base变量和cell数组变量,通过hash对Cell数组初始化和累加操作,最后将base和Cell数组所有的数进行累加得到结果。

<think>我们正在讨论Java中Map接口的实现原理和常用实现类的工作机制。根据引用内容,Map用于存储键值对,常用实现类有HashMap、TreeMap、Hashtable等。其中,HashMap是最常用的,它基于哈希表实现,结合了数组和链表(在JDK8之后还引入了红黑树)。TreeMap基于红黑树实现,而Hashtable是线程安全的哈希表实现。首先,我们来看HashMap的实现原理:1.数据结构:HashMap底层是一个数组(Node<K,V>[]table),数组的每个位置称为桶(bucket)。每个桶可以存放一个链表或一棵红黑树(当链表长度超过阈值时,默认为8,且数组长度大于等于64时,链表会转换为红黑树,以减少搜索时间)。2.哈希函数:通过键(key)的hashCode()计算哈希值,然后通过扰动函数(在JDK8中是先高16位与低16位异或,再与数组长度取模)来减少哈希冲突。3.插入元素:根据键的哈希值确定桶的位置,如果该位置为空,则直接放入;否则,遍历链表或树,如果找到相同的键(通过equals()判断),则更新值;否则,将新节点插入链表末尾或树中。4.扩容机制:当元素数量超过阈值(容量*负载因子,默认负载因子0.75)时,数组会扩容为原来的2倍,并重新计算每个元素的位置。然后,TreeMap基于红黑树(一种自平衡的二叉搜索树)实现,因此它能够保持键的有序性(自然顺序或通过Comparator指定的顺序)。插入、删除和查找操作的时间复杂度为O(logn)。Hashtable是线程安全的,它使用synchronized来保证同步,但性能较低。它不允许键或值为null,而HashMap允许。Properties是Hashtable的子类,主要用于读取配置文件。现在,我们来组织回答,并按照要求生成相关问题。注意:回答中需要引用时,在引用内容末尾添加引用标识,例如[^1]。</think>### Java Map接口的实现原理及常用实现类工作机制 #### 一、Map接口核心原理 Map接口的核心是**键值对存储机制**,通过哈希函数实现高效存取。当调用`put(key, value)`时: 1. 计算键的哈希值:`int hash = hash(key.hashCode())`(JDK8引入二次哈希扰动,减少碰撞[^3]) 2. 定位桶位置:`index = (n-1) & hash`(n为数组长度) 3. 处理哈希冲突: - JDK7:数组+链表(头插法) - JDK8+:数组+链表/红黑树(链表长度>8且数组长度≥64时树化[^4]) #### 二、主要实现类工作机制 1. **HashMap(最常用)** - **数据结构**:数组+链表/红黑树 - **特性**: - 允许`null`键和`null`值 - 非线程安全(并发修改可能导致死循环[^3]) - 默认初始容量16,负载因子0.75(扩容阈值=容量×负载因子) - **扩容机制**:当元素数超过阈值时,新建2倍数组并重新散列(JDK8优化:高位链表直接迁移[^4]) 2. **Hashtable(线程安全版)** - 与HashMap主要差异: - 所有方法用`synchronized`修饰(线程安全但性能低) - 不允许`null`键值(否则抛`NullPointerException`[^5]) - 初始容量11,扩容公式:`newCapacity = (oldCapacity << 1) + 1` 3. **TreeMap(有序映射)** - **数据结构**:红黑树(自平衡二叉搜索树) - **特性**: - 按键的自然顺序或Comparator排序 - 操作时间复杂度O(log n) - 支持子映射视图(如`subMap()`) 4. **ConcurrentHashMap(高并发场景首选)** - JDK7:分段锁(Segment数组) - JDK8+:CAS+synchronized锁桶头节点 - 读操作无锁,写操作仅锁单个桶[^2] #### 三、关键设计对比 | 特性 | HashMap | Hashtable | TreeMap | |--------------------|----------|-----------|---------------| | **线程安全** | × | √ | × | | **允许null键值** | √ | × | 仅值可为null | | **底层结构** | 哈希表 | 哈希表 | 红黑树 | | **时间复杂度** | O(1)~O(n)| O(1)~O(n) | O(log n) | | **排序** | 无序 | 无序 | 自然/定制排序 | > 最佳实践:优先使用`Map<String, Integer> map = new HashMap<>()`声明接口类型,提高代码复用性[^2]。 #### 四、典型应用场景 - **HashMap**:缓存实现、无状态数据存储 - **TreeMap**:范围查询(如时间区间数据) - **ConcurrentHashMap**:高并发计数器、缓存 - **Properties**(Hashtable子类):配置文件读取[^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值