如何解决多线程环境下“指针碰撞”带来的并发问题

指针碰撞是一种在内存规整情况下进行对象内存分配的高效方式,但在多线程环境下使用指针碰撞会引发并发问题。

并发问题产生的原因

在指针碰撞的内存分配机制中,存在一个分界指针用于区分已使用内存未使用内存。当有新对象需要分配内存时,只需将这个指针向未使用内存的方向移动与对象大小相等的距离。

而指针的 读取-修改-回写 操作需要不是原子的。所以,在多线程环境下,多个线程可能会同时尝试分配内存,会同时操作这个分界指针。由于线程执行的不确定性,如果多个线程同时读取到了相同的指针位置,并且都基于这个位置进行内存分配,就会导致多个线程为不同的对象分配到同一块内存区域,从而引发内存分配错误和数据混乱等问题。

比如当前分界指针指向内存地址为 100 的位置,有两个线程 Thread AThread B 同时要分配大小为 10 的内存空间。这两个线程可能会同时读取到指针位置为 100,然后都将指针向后移动 10 个单位,这样就会导致两个线程都认为自己分配到了地址从 100 到 109 的内存区域(此时指针更新到 110),实际上这就造成了内存分配的冲突。
在这里插入图片描述

常见的解决办法

1. CAS(Compare-And-Swap)

CAS 是一种乐观锁机制,它包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。在进行内存分配时,线程会先读取当前指针的值作为预期原值(A),然后尝试将指针的值更新为新的位置(B)。在更新之前,会比较指针当前的实际值(V)是否与预期原值(A)相等。如果相等,说明没有其他线程修改过指针的值,就可以安全地更新指针;如果不相等,说明有其他线程已经修改了指针,当前线程需要重新读取指针的值并再次尝试更新。

// 模拟内存分配
class MemoryAllocator {
    private AtomicInteger pointer; // 使用 AtomicInteger 实现 CAS

    public MemoryAllocator(int initialPointer) {
        this.pointer = new AtomicInteger(initialPointer);
    }

    // 分配内存的方法
    public int allocate(int size) {
        while (true) {
            int current = pointer.get(); // 获取当前指针位置
            int next = current + size; // 计算新的指针位置
            // 使用 CAS 尝试更新指针
            if (pointer.compareAndSet(current, next)) {
                return current; // 分配成功,返回分配的内存起始地址
            }
            // CAS 失败,说明有其他线程修改了指针,重试
        }
    }
}

// 测试
public class PointerCollisionTest {
    public static void main(String[] args) {
        MemoryAllocator allocator = new MemoryAllocator(100);

        // 创建多个线程进行内存分配
        Thread thread1 = new Thread(() -> {
            int address = allocator.allocate(10);
            System.out.println("Thread 1 allocated memory at begin address: " + address);
        });

        Thread thread2 = new Thread(() -> {
            int address = allocator.allocate(20);
            System.out.println("Thread 2 allocated memory at begin address: " + address);
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上述代码中,AtomicInteger 类提供了 CAS 操作的支持,compareAndSet 方法会比较当前指针的值是否与预期值相等,如果相等则更新指针的值。

在这里插入图片描述

2. TLAB(Thread Local Allocation Buffer)

TLAB 是一种线程私有的内存分配缓冲区。每个线程在 Java 堆中预先分配一块小的内存区域作为自己的 TLAB,线程在分配对象时,首先会尝试在自己的 TLAB 中进行分配。由于 TLAB 是线程私有的,所以在 TLAB 内进行内存分配时不会产生并发问题。只有当 TLAB 空间不足时,线程才会去全局堆中进行分配,此时可能会使用 CAS 等方式来解决并发问题。

通过使用 TLAB,可以减少多线程之间的竞争提高内存分配的效率。Java 虚拟机默认开启了 TLAB 机制,当然也可以通过 -XX:+UseTLAB 参数来显式启用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值