06 - 多线程-JUC并发编程-原子类(二)

news2025/4/19 7:32:57

        上一章,讲解java (java.util.concurrent.atomic) 包中的 支持基本数据类型的原子类,以及支持数组类型的原子类,这一章继续讲解支持对实体类的原子类,以及原子类型的修改器。

        还有最后java (java.util.concurrent.atomic) 包中对累计加载计算的功能专门提供性能特别高的工具类:LongAccumulator 和 LongAdder。

一,数组类型的原子类

        在Java的J.U.C的原子包中,提供了支持对象的原子类操作的。类型如下:

 AtomicReference、AtomicStampedReference,AtomicMarkableReference

1.1 AtomicReference 案例

package com.toast.javase.source.thread.juc.atomic;

import java.util.concurrent.atomic.AtomicReference;

/**
 * @author liuwq
 * @time 2025/4/1
 * @remark
 */
class User {
    String name;
    int age;
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String toString() {
        return name + " - " + age;
    }
}

public class AtomicReferenceExample {

    public static void main(String[] args) {
        AtomicReference<User> atomicUser = new AtomicReference<>(new User("张三", 20));

        User newUser = new User("李四", 25);
        boolean updated = atomicUser.compareAndSet(atomicUser.get(), newUser); // CAS 操作

        System.out.println("修改成功:" + updated);
        System.out.println("当前用户:" + atomicUser.get());
    }
}

输出结果:

修改成功:true
当前用户:李四 - 25

解释

  • compareAndSet(expected, newValue) 方法会检查当前值是否等于 expected,如果相等,则更新为 newValue,否则失败。
  • 适用于简单的引用更新,但可能会遇到 ABA 问题(即值从 A → B → A,导致 compareAndSet 误以为没有变化)。

1.2 AtomicReference 模拟ABA问题案例

public class AtomicReferenceABA {
    public static void main(String[] args) {
        User zs = new User("张三", 20);
        User ls = new User("李四", 23);
        User ww = new User("王五", 25);
        AtomicReference<User> atomicRef = new AtomicReference<>(zs);

        Thread t1 = new Thread(() -> {
            atomicRef.compareAndSet(zs, ls); // zs -> ls
            atomicRef.compareAndSet(ls, zs); // ls -> zs (ABA 发生)
        });

        Thread t2 = new Thread(() -> {
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}

            boolean success = atomicRef.compareAndSet(zs, ww); // zs -> ww,理论上不应该成功
            System.out.println("修改成功:" + success);
            System.out.println("当前用户:" + atomicRef.get());
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException ignored) {}
    }
}

输出结果:

修改成功:true
当前用户:王五 - 25

解释:

    t1 执行 zs -> ls -> zs,但 对象引用没有变化(还是 zs)。

    t2 误以为 zs 还是最初的 zs,但其实它已经被 ls 覆盖过一次了。

    compareAndSet(zs, ww) 成功了,导致 t2 不知情地修改了已经变更过的数据,这就是 ABA 问题。

1.3 AtomicStampedReference 示例

适用场景

  • 解决 ABA 问题,因为它在更新引用的同时,维护一个 时间戳(版本号)
/**
 * @author liuwq
 * @time 2025/4/1
 * @remark
 */
public class AtomicStampedReferenceExample {
    public static void main(String[] args) {
        User zs = new User("张三", 20);
        User ls = new User("李四", 23);
        User ww = new User("王五", 25);
        int initialStamp = 1;
        AtomicStampedReference<User> atomicStampedRef = new AtomicStampedReference<>(zs, initialStamp);

        Thread t1 = new Thread(() -> {
            int stamp = atomicStampedRef.getStamp(); // 获取当前时间戳
            atomicStampedRef.compareAndSet(zs, ls, stamp, stamp + 1); // zs -> ls
            atomicStampedRef.compareAndSet(ls, zs, stamp + 1, stamp + 2); // ls -> zs (ABA 发生)
        });

        Thread t2 = new Thread(() -> {
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}

            int stamp = atomicStampedRef.getStamp();
            boolean success = atomicStampedRef.compareAndSet(zs, ww, stamp, stamp + 1); // zs -> ww

            System.out.println("修改成功:" + success);
            System.out.println("当前时间戳:" + atomicStampedRef.getStamp());
            System.out.println("当前用户:" + atomicStampedRef.getReference());
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException ignored) {}
    }
}

输出结果:

修改成功:true
当前时间戳:4
当前用户:王五 - 25

     尽管当前用户被修改成 王五 - 25,但是当前时间戳的版本是4意味着之前张三被李四覆盖的修改被记录了,所以并没有发生ABA问题。

解析:

  • AtomicStampedReference 代码 不会触发 ABA,因为 t1改变了时间戳,而 t2 获取的时间戳是最新的 3,所以它的 CAS 是安全的。
  • 真正的 ABA 问题 发生在 时间戳未变化 的情况下,比如 AtomicReference,它只比较对象引用,不关心数据是否被改动过。

1.4 AtomicMarkableReference 模拟购物车中的商品软删除

适用场景

  • 适用于需要使用布尔标记来表示某个对象是否被修改或删除的场景(例如:延迟删除、软删除)。

业务场景:购物车中的商品软删除

     在电商系统中,用户可以将商品加入购物车。如果用户删除商品,我们希望:

  1. 先标记商品为 "待删除" 状态(类似于软删除)。
  2. 后台任务在适当时候(如 30 分钟后)真正删除商品

【真正的问题】

  • 用户可能在 "待删除" 状态下 撤销删除,即取消软删除。
  • 需要保证并发情况下,删除操作不会误删仍在使用的商品。

解决方案:使用 AtomicMarkableReference

  • AtomicMarkableReference<T> 允许对一个对象(如 CartItem)和一个布尔标记(表示是否待删除)进行原子操作
  • 如果用户删除商品,系统不会立即移除,而是先标记
  • 后台任务检查标记后,真正删除商品,但前提是用户没有撤销删除。
package com.toast.javase.source.thread.juc.atomic;

import java.util.concurrent.atomic.AtomicMarkableReference;

/**
 * @author liuwq
 * @time 2025/4/1
 * @remark
 */
class CartItem {
    String productId;
    String name;

    public CartItem(String productId, String name) {
        this.productId = productId;
        this.name = name;
    }

    @Override
    public String toString() {
        return "CartItem{" + "productId='" + productId + "', name='" + name + "'}";
    }
}

public class AtomicMarkableReferenceExample {
    public static void main(String[] args) throws InterruptedException {
        CartItem item = new CartItem("5090XT", "Laptop");
        AtomicMarkableReference<CartItem> cartItemRef = new AtomicMarkableReference<>(item, false);

        // 用户删除商品(设置标记为 true)
        Thread userDeletesItem = new Thread(() -> {
            boolean success = cartItemRef.compareAndSet(item, item, false, true);
            System.out.println("用户删除" + item.productId + "商品:" + success + ",当前标记:" + cartItemRef.isMarked());
        });

        // 用户撤销删除(恢复标记为 false)
        Thread userRestoresItem = new Thread(() -> {
            boolean success = cartItemRef.compareAndSet(item, item, true, false);
            System.out.println("用户撤销删除" + item.productId + ":" + success + ",当前标记:" + cartItemRef.isMarked());
        });

        // 后台任务检查标记并尝试真正删除
        Thread backgroundCleanup = new Thread(() -> {
            try { Thread.sleep(1000); } catch (InterruptedException ignored) {}

            boolean[] markHolder = new boolean[1];
            CartItem currentItem = cartItemRef.get(markHolder);

            if (markHolder[0]) { // 只有标记仍为 true 才能删除
                boolean deleted = cartItemRef.compareAndSet(currentItem, null, true, false);
                System.out.println("后台真正删除" + item.productId + "商品:" + deleted + ",当前商品:" + cartItemRef.getReference());
            } else {
                System.out.println("后台检查:" + item.productId + "商品未被删除,保持原状");
            }
        });

        userDeletesItem.start();
        userRestoresItem.start();
        userDeletesItem.join();
        userRestoresItem.join();
        backgroundCleanup.start();
        backgroundCleanup.join();
    }
}

输出结果:

用户撤销删除5090XT:true,当前标记:false
用户删除5090XT商品:true,当前标记:true
后台检查:5090XT商品未被删除,保持原状

        这个方案可以有效解决并发环境下的软删除问题,避免因用户操作冲突导致错误删除!🚀

二,原子类型的属性修改器

        在一个类之中可能会存在有若干不同的属性,但是有可能在进行线程同步处理的时候不是该类中所有的属性度会被进行所谓的同步操作,只有部分的属性需要进行同步的处理操作,所以在J.U.C提供的原子类型里面,就包含有一个属性修改器,利用属性修改器可以安全的修改属性的内容,原子开发包中提供的属性修改器一共包含有三种:

public abstract class AtomicIntegerFieldUpdater<T> {}

public abstract class AtomicLongFieldUpdater<T> {}

public abstract class AtomicReferenceFieldUpdater<T,V> {}

        这三个 AtomicFieldUpdater 适用于需要 对类的字段进行原子更新,但又不想使用 volatile 变量或者 AtomicInteger 等包装类的场景。它们的优势:

  • 减少对象开销:避免将单个整数或对象包装成 AtomicIntegerAtomicReference,直接对类的字段进行原子操作。
  • 支持并发更新:适用于高并发场景,如计数器、状态更新等。
  • 可用于非 final 变量:但必须是 volatile 修饰的字段,以确保可见性。

2.1 场景 1:网站 PV(页面访问量)计数

  • 使用 AtomicIntegerFieldUpdater,避免给每个页面创建一个 AtomicInteger 实例,减少对象开销。
  • 高并发场景下,多个线程同时更新 pageViews,保证安全。
/**
 * @author liuwq
 * @time 2025/4/1
 * @remark 网站 PV(页面访问量)计数
 * ● 使用 AtomicIntegerFieldUpdater,避免给每个页面创建一个 AtomicInteger 实例,减少对象开销。
 * ● 高并发场景下,多个线程同时更新 pageViews,保证安全。
 */
class WebPage {
    private String url;
    volatile int pageViews = 0; // 必须是 volatile

    public WebPage(String url) {
        this.url = url;
    }

    public String getUrl() {
        return url;
    }
}

public class AtomicIntegerFieldUpdaterExample {
    private static final AtomicIntegerFieldUpdater<WebPage> pageViewUpdater =
            AtomicIntegerFieldUpdater.newUpdater(WebPage.class, "pageViews");

    public static void main(String[] args) throws InterruptedException {
        WebPage page = new WebPage("https://example.com");

        // 模拟多线程访问
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    pageViewUpdater.incrementAndGet(page); // 原子增加 PV
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("最终访问量:" + page.pageViews);
    }
}

输出结果:

最终访问量:1000

适用业务场景

  • 高并发 PV 计数:避免使用锁,提高性能。
  • 热点统计数据:如点赞数、访问量、点击量等。

2.2 场景2:订单状态管理

  • 使用 AtomicLongFieldUpdater,用于高并发修改订单状态。
  • 示例:订单从 "创建" -> "支付" -> "完成" 状态的原子更新
class Order {
    private String orderId;
    volatile long status = 0; // 0=创建, 1=支付, 2=完成

    public Order(String orderId) {
        this.orderId = orderId;
    }

    public String getOrderId() {
        return orderId;
    }
}

public class AtomicLongFieldUpdaterExample {
    private static final AtomicLongFieldUpdater<Order> statusUpdater =
            AtomicLongFieldUpdater.newUpdater(Order.class, "status");

    public static void main(String[] args) {
        Order order = new Order("ORDER123");

        // 线程1:支付订单
        Thread t1 = new Thread(() -> {
            boolean success = statusUpdater.compareAndSet(order, 0, 1);
            System.out.println("支付成功:" + success + ",当前状态:" + order.status);
        });

        // 线程2:完成订单
        Thread t2 = new Thread(() -> {
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}

            boolean success = statusUpdater.compareAndSet(order, 1, 2);
            System.out.println("订单完成:" + success + ",当前状态:" + order.status);
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException ignored) {}
    }
}

输出结果:

支付成功:true,当前状态:1
订单完成:true,当前状态:2

适用业务场景

  • 订单状态管理:如从 创建 -> 处理中 -> 完成,确保状态变更的原子性。
  • 任务流转:如 任务从待处理 -> 进行中 -> 完成

2.3 场景3:动态切换系统配置

  • 使用 AtomicReferenceFieldUpdater原子替换配置信息
  • 示例:更新系统的数据库配置,并确保其他线程能安全读取最新的配置。

/**
 * @author liuwq
 * @time 2025/4/1
 * @remark
 */
class Config {
    volatile DatabaseConfig dbConfig;

    public Config(DatabaseConfig initialConfig) {
        this.dbConfig = initialConfig;
    }
}

class DatabaseConfig {
    String url;
    String username;
    String password;

    public DatabaseConfig(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }

    @Override
    public String toString() {
        return "DatabaseConfig{url='" + url + "', user='" + username + "'}";
    }
}

public class AtomicReferenceFieldUpdaterExample {
    private static final AtomicReferenceFieldUpdater<Config, DatabaseConfig> configUpdater =
            AtomicReferenceFieldUpdater.newUpdater(Config.class, DatabaseConfig.class, "dbConfig");

    public static void main(String[] args) {
        Config systemConfig = new Config(new DatabaseConfig("jdbc:mysql://old-db", "root", "123456"));

        System.out.println("初始配置:" + systemConfig.dbConfig);

        // 线程1:更新数据库配置
        Thread updateThread = new Thread(() -> {
            DatabaseConfig newConfig = new DatabaseConfig("jdbc:mysql://new-db", "admin", "654321");
            boolean success = configUpdater.compareAndSet(systemConfig, systemConfig.dbConfig, newConfig);
            System.out.println("数据库配置更新:" + success);
        });

        // 线程2:读取最新配置
        Thread readThread = new Thread(() -> {
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}
            System.out.println("最新配置:" + systemConfig.dbConfig);
        });

        updateThread.start();
        readThread.start();

        try {
            updateThread.join();
            readThread.join();
        } catch (InterruptedException ignored) {}
    }
}

适用业务场景

  • 动态配置切换:如数据库连接配置、缓存策略等。
  • 原子更新全局状态:如切换日志级别、热更新服务配置。
初始配置:DatabaseConfig{url='jdbc:mysql://old-db', user='root'}
数据库配置更新:true
最新配置:DatabaseConfig{url='jdbc:mysql://new-db', user='admin'}

        这三个 AtomicFieldUpdater 类提供了一种轻量级的原子性操作方式,适用于并发场景下的字段更新,避免了 AtomicInteger 这样的独立对象带来的额外开销!🚀

        并且如果需要在多线程并发的环境下去修改属性,那对应的属性需要被 volatile 关键字所修改。这样就保证了被修改的数据在线程之间的可见性。且保证了一定的指令重排作用。

三,LongAccumulator & LongAdder & Striped64

        在Java (J.U.C并发包)之中为计数器专门提供了高性能计数器,主要用于高并发场景,解决 AtomicLong 原子类在高并发竞争环境下的性能瓶颈。

3.1. LongAdder

📌 适用场景

  • 计数、累加(如访问量统计、计数器等)
  • 高并发情况下减少 CAS 失败,比 AtomicLong 性能更优

✅ 特点

  • 支持多线程高并发累加,使用分段累加(Cell 数组)
  • 最终获取总和时才做合并,提升写入性能
  • 不支持减法(只能增加)

🚀 示例:网站访问量统计

/**
 * @author liuwq
 * @time 2025/4/1
 * @remark
 */
public class LongAdderExample {
    private static final LongAdder counter = new LongAdder();

    public static void main(String[] args) throws InterruptedException {
        int threadCount = 10;
        Thread[] threads = new Thread[threadCount];

        // 每个线程执行 10000 次累加
        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    counter.increment();
                }
            });
        }

        for (Thread thread : threads) thread.start();
        for (Thread thread : threads) thread.join();

        System.out.println("最终计数值:" + counter.sum()); // 100000
    }
}

输出结果:

最终计数值:100000

📝 解释:

  • increment():每个线程局部累加,减少竞争
  • sum():合并所有分片的值,获取最终结果
  • AtomicLong.incrementAndGet() 性能更优

3.2. LongAccumulator

📌 适用场景

  • 自定义计算逻辑(不是单纯累加,可以做乘法、最小值、最大值等)
  • 累加器模式(不同线程可提供不同计算逻辑)

✅ 特点

  • 需要提供自定义函数
  • 适用于非加法计算,如乘法、最大值、最小值等

🚀 示例:计算最大值

public class LongAccumulatorExample {
    public static void main(String[] args) throws InterruptedException {
        // 自定义函数:取较大值
        LongBinaryOperator maxFunc = Math::max;
        LongAccumulator maxValue = new LongAccumulator(maxFunc, Long.MIN_VALUE);

        int threadCount = 5;
        Thread[] threads = new Thread[threadCount];

        long[] values = {10, 20, 50, 5, 100};

        for (int i = 0; i < threadCount; i++) {
            final int index = i;
            threads[i] = new Thread(() -> maxValue.accumulate(values[index]));
        }

        for (Thread thread : threads) thread.start();
        for (Thread thread : threads) thread.join();

        System.out.println("最大值:" + maxValue.get()); // 100
    }
}

输出结果:

最大值:100

3.3. LongAdder vs LongAccumulator

特性

LongAdder

LongAccumulator

默认操作

仅支持 累加

可自定义,如最大值、最小值、乘法

适用场景

计数器(如访问量统计、累加任务)

复杂计算(如最大值、最小值)

并发优化

分段累加,合并时计算

自定义合并逻辑

线程安全


3.4. 什么时候用哪一个?

  • 需要做高并发计数LongAdder
  • 需要自定义计算逻辑(最大值、最小值、乘法等)LongAccumulator
  • DoubleAccumulatorLongAccumulator支持两种数据类型

📌 总结

  • LongAdder 适用于高并发累加,不支持减法
  • LongAccumulator 适用于自定义计算(最大值、最小值等)
  • AtomicLong 更适合高并发场景

这样选择就更清晰啦!💡🚀

3.5 Striped64 讲解

   Striped64LongAdderLongAccumulator 等类的父类,这些类用来处理高并发场景下的数值计算(如计数器、累加器等)。Striped64 的主要设计目标是减少锁竞争,提高性能,特别是在多线程高并发的环境下。

          而刚刚我们提到了LongAdderLongAccumulator解决了对应的原子类比 AtomicLong 性能瓶颈,性能更优。其原因就是拥有着Striped64 支持。引入了分段锁的思想。

          那多问一个问题AtomicLong 等原子类的性能瓶颈是多少,体现在哪里?为什么是通过引入分段锁的思想来优化。

AtomicLong 等原子类的性能瓶颈及优化

   AtomicLong 和其他原子类(如 AtomicIntegerAtomicReference 等)是用于在多线程环境中实现原子操作的类,它们通过底层的 CAS(Compare-And-Swap)机制确保了线程安全,而不需要使用传统的锁机制(如 synchronized)。然而,在高并发场景下,即使是原子类也会有性能瓶颈。了解这些瓶颈并采取优化措施非常重要。

🔹 1. 性能瓶颈:

        在多线程并发环境下,AtomicLong 等原子类的性能瓶颈主要体现在以下几个方面:

1.1 单个共享变量的争用

   AtomicLong 等原子类通过 CAS 操作确保线程安全,当多个线程竞争修改同一个共享变量时,CAS 会尝试将当前变量的值与期望值进行比较并交换。如果多个线程同时尝试更新该变量的值,它们的操作会产生竞争,并且只有一个线程能够成功更新变量值,其它线程则需要重新进行 CAS 操作。

  • 问题:如果多个线程频繁访问同一个原子变量,CAS 操作的失败会增加 CPU 的消耗,导致大量的缓存一致性问题内存屏障。尤其是在高并发情况下,这种竞争会极大地降低性能。
1.2 伪共享

       伪共享是指多个线程操作的不同变量在内存中的缓存行(cache line)相同,从而导致缓存一致性问题。在 AtomicLong 等原子类中,如果多个线程同时修改某个共享变量,并且这些变量的位置位于同一缓存行中,则会导致频繁的缓存失效(即缓存行的同步)。

  • 问题:在多个线程频繁修改变量时,缓存一致性会变得非常昂贵,从而影响性能。

🔹 3. 分段锁的具体实现:

      以 LongAdder 为例,它继承自 Striped64,使用了分段计数器:

  • Cell[] cells:是 Striped64 中的一个数组,存储多个 Cell(每个 Cell 具有一个 AtomicLong),每个线程通常会选择一个不同的 Cell 进行操作。
  • base:当 Cell[] 中的所有 Cell 的值都合并完成时,base 中的值会保存最终的累加结果。

       这样,在高并发的场景下,线程间的竞争被分散到多个 Cell,从而减少了锁竞争和伪共享的问题。

🔹 4. 总结

   AtomicLong 等原子类在高并发环境中面临的性能瓶颈主要是由于竞争、CAS 操作的失败以及伪共享问题。为了优化这些问题,引入了分段锁的思想(如在 LongAdderLongAccumulator 中),通过将数据分为多个小的原子计数器,减少了线程间的竞争,提高了性能。这种方式减少了对单一变量的争用,并避免了缓存行的伪共享,从而在高并发情况下提供了显著的性能提升。

🔹 1. Striped64 类简介

   Striped64 的核心思想是:通过将数据分段来减少线程间的竞争。它维护了一个 Cell[] 数组,其中每个 Cell 是一个小的原子计数器。每个线程操作不同的 Cell,这样避免了多个线程同时操作同一个计数器,从而降低了锁竞争。

   Striped64 本身并不直接提供外部可见的接口(如 increment()accumulate() 等),它主要为 LongAdderLongAccumulator 提供基础实现。

2. 主要实现细节

  • 分段原子性Striped64 使用了多个原子变量 (Cell[]),每个 Cell 是一个具有原子性操作的 AtomicLong
  • 合并操作:当计算完成时,Striped64 会合并所有 Cell 中的结果以得到最终的值。

       在 LongAdderLongAccumulator 中,具体的操作(如累加或自定义计算)都会基于 Striped64 来实现数据的分段管理,从而有效地避免了竞争问题。

四,VarHandler

       在Java并发包(java.util.concurrent.atomic)之中,该atomic原子包下的原子类,其原子性是通过无锁机制的CAS来保证其原子性。而提供该原子性的操作的类是Unsafe类,但是在AtomicReferenceArray类之中却发现,提供CAS操作的类并非Unsafe类,而是VarHandler 类。

       其他的原子类也是一样的将Unsafe 替换为VarHandler类。

public class AtomicReferenceArray<E> implements java.io.Serializable {
    private static final long serialVersionUID = -6209656149925076980L;
    private static final VarHandle AA  = MethodHandles.arrayElementVarHandle(Object[].class);
}

观察VarHandler类

/**
 * @since 9 JDK9 提供的
 */
public abstract class VarHandle implements Constable {/** 忽略其他代码 */} 

4.1 Unsafe与VarHandler的区别

   UnsafeVarHandle 都是 Java 中用于直接操作内存和变量的一些类,它们在处理低级内存操作时提供了非常强大的功能。然而,它们有一些关键的区别,特别是在设计、使用场景和安全性方面。

1. Unsafe

   Unsafe 类是 sun.misc 包中的一个类,提供了一组直接访问底层操作系统和硬件的API。它通常用于执行一些非常底层的操作,比如直接内存操作、原子操作、内存屏障、对象实例化等,Java应用程序通常不需要直接使用它,但它被一些框架(如 NettyConcurrent 类库等)用来优化性能。

主要功能:
  • 直接内存操作:可以通过 allocateMemory() 等方法直接分配、读取和修改内存。
  • 对象字段访问:可以通过 getObject()putObject() 等方法直接访问对象的字段,不受 privatefinal 限制。
  • 原子操作:提供了 compareAndSwap(CAS)等方法进行原子性更新。
  • 类的实例化:可以绕过 constructor 直接分配对象内存和初始化对象。
局限性和问题:
  • 不安全Unsafe 是一个非常底层的工具,容易引发错误并导致内存泄漏或崩溃。因为它绕过了 Java 的垃圾回收和内存管理机制,所以使用不当可能会导致严重的问题。
  • 与平台相关:它依赖于底层操作系统,使用 Unsafe 的代码往往是与平台紧密耦合的,可能会在不同的JVM或操作系统上有不同的行为。
  • 不再推荐使用:虽然它非常强大,但通常不建议直接使用 Unsafe,而是通过提供更安全和高层的API(如 VarHandle)来替代。

2. VarHandle

   VarHandle 是 Java 9 引入的一个类,它提供了对变量(包括字段、数组元素、以及静态变量)进行底层操作的功能。VarHandle 是对 Unsafe 功能的改进,提供了一种更安全、类型安全的方式来进行低级别的内存访问,特别是为了替代 Unsafe 的一些操作。

主要功能:
  • 类型安全:与 Unsafe 不同,VarHandle 在编译时就检查类型,因此更加安全。你不能直接操作原始类型,而必须遵循类型规则。
  • 支持原子操作VarHandle 提供了对字段的原子性访问操作(类似于 Atomic 类),包括支持 compareAndSet()getAndAdd() 等操作。
  • 支持并发编程:提供了一些用于并发控制的功能,如内存屏障、原子操作等。
  • 字段更新与访问:你可以使用 VarHandle 来读取、写入甚至修改对象字段的值。它可以跨类、跨方法地安全地访问变量。
优势:
  • 安全性VarHandle 提供了更高层次的抽象和类型检查,使用时不会像 Unsafe 那样绕过 Java 的类型系统。
  • 灵活性:可以通过 VarHandle 来操作各种类型的字段(普通字段、静态字段、数组元素等),且这些操作通常是并发安全的。
  • 跨平台支持:相比于 UnsafeVarHandle 更加通用,能确保在不同的平台上具有一致性。

3. 主要区别总结

特性

Unsafe

VarHandle

引入版本

Java 1.0(但不建议直接使用)

Java 9

类型安全性

不安全,绕过 Java 的类型检查,使用时容易出错。

类型安全,提供了类型检查。

操作粒度

支持直接的内存操作、对象字段、数组操作等,功能强大但容易出错。

主要用于变量字段的操作,支持原子操作,功能更专注。

并发操作支持

支持原子操作,但需要手动实现并发控制。

内置对原子操作的支持(如 compareAndSet

),更适合并发场景。

使用难度

难度较大,风险高,容易造成崩溃或错误。

更易于使用和理解,避免了很多潜在的风险。

平台依赖性

与平台和操作系统相关,可能在不同的 JVM 上表现不同。

设计为跨平台,不依赖于特定平台或操作系统。

可替代性

Unsafe

是较低级的操作,更多用于高性能库和框架。

VarHandle

是一个较高层的 API,逐渐成为推荐的替代方案。


4. 何时使用 UnsafeVarHandle

  • 使用 Unsafe:仅在你需要极限性能优化,并且确切知道自己在做什么的情况下。Unsafe 允许绕过 JVM 的很多安全和类型检查,因此很容易出错,建议仅在需要进行底层优化时使用,比如构建高性能的并发工具或底层库(如 Netty、Disruptor 等)。
  • 使用 VarHandle:在 Java 9 及以上版本中,推荐使用 VarHandle,因为它提供了安全、类型安全的原子操作,并且能够避免 Unsafe 的许多危险用法。对于现代并发编程,VarHandle 提供了比 Unsafe 更好的性能和更高的可维护性。

5. 示例代码:

public class UnsafeCounterExample {
    private static final Unsafe unsafe;

    static {
        try {
            // 获取 Unsafe 实例
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    private long counter = 0; // 需要进行操作的字段

    // 定义字段的内存偏移量
    private static final long COUNTER_OFFSET;

    static {
        try {
            // 确保 counter 字段存在,并获取它的内存偏移量
            COUNTER_OFFSET = unsafe.objectFieldOffset(UnsafeCounterExample.class.getDeclaredField("counter"));
        } catch (NoSuchFieldException e) {
            throw new Error("No such field: counter", e);
        } catch (Exception e) {
            throw new Error("Unexpected error", e);
        }
    }

    // 原子地增加 counter
    public void increment() {
        unsafe.getAndAddLong(this, COUNTER_OFFSET, 1); // 使用 Unsafe 直接操作 long 类型字段
    }

    public static void main(String[] args) {
        UnsafeCounterExample example = new UnsafeCounterExample();
        example.increment();
        System.out.println("Counter: " + example.counter); // 输出: Counter: 1
    }
}
public class VarHandleExample {
    private long counter = 0;

    // 定义一个 VarHandle 来操作字段
    private static final VarHandle COUNTER;

    static {
        try {
            // 使用 MethodHandles.lookup() 获取字段的 VarHandle 实例
            COUNTER = MethodHandles.lookup().findVarHandle(VarHandleExample.class, "counter", long.class);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException("VarHandle initialization failed", e);
        }
    }

    public void increment() {
        COUNTER.getAndAdd(this, 1); // 使用 VarHandle 进行原子操作
    }

    public static void main(String[] args) {
        VarHandleExample example = new VarHandleExample();
        example.increment();
        System.out.println("Counter: " + example.counter); // 输出: Counter: 1
    }
}

结论:

  Unsafe 是一个底层的工具,适用于极限性能优化,但使用不当可能导致严重的内存问题。VarHandle 则提供了更为安全、简洁的接口,适用于并发操作中的字段访问,尤其在 Java 9 及以后版本,推荐使用 VarHandle 来替代 Unsafe

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2336150.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SOME/IP中”客户端消费“及”服务端提供”的解析

先上结论 AREthAddConsumedEventGroup-->客户端的函数-->谁调用 Consumed函数&#xff0c;谁就是消费者 AREthAddProvidedEventGroup-->服务端的函数-->谁调用 Provided函数&#xff0c;谁就是服务端 Server 端&#xff1a;AREthAddProvidedEventGroup → 声明 &…

Linux 深入浅出信号量:从线程到进程的同步与互斥实战指南

知识点1【信号量概述】 信号量是广泛用于进程和线程间的同步和互斥。信号量的本质 是一个非负的整数计数器&#xff0c;它被用来控制对公共资源的访问 当信号量值大于0的时候&#xff0c;可以访问&#xff0c;否则将阻塞。 PV原语对信号量的操作&#xff0c;一次P操作使信号…

Oracle数据库数据编程SQL<9.1 数据库逻辑备份和迁移exp和imp之导出、导入>

EXP (Export) 和 IMP (Import) 是 Oracle 提供的传统数据导出导入工具,用于数据库逻辑备份和迁移。尽管在较新版本中已被 Data Pump (EXPDP/IMPDP) 取代,但在某些场景下仍然有用。 目录 一、EXP 导出工具 1. 基本语法 2. 常用参数说明 3. 导出模式 3.1 表模式导出 3.2 用…

DotnetCore开源库SampleAdmin源码编译

1.报错: System.Net.Sockets.SocketException HResult0x80004005 Message由于目标计算机积极拒绝&#xff0c;无法连接。 SourceSystem.Net.Sockets StackTrace: 在 System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, C…

.Net 9 webapi使用Docker部署到Linux

参考文章连接&#xff1a; https://www.cnblogs.com/kong-ming/p/16278109.html .Net 6.0 WebApi 使用Docker部署到Linux系统CentOS 7 - 长白山 - 博客园 项目需要跨平台部署&#xff0c;所以就研究了一下菜鸟如何入门Net跨平台部署&#xff0c;演示使用的是Net 9 webAPi Li…

PyTorch 根据官网命令行无法安装 GPU 版本 解决办法

最近遇到一个问题&#xff0c;PyTorch 官网给出了 GPU 版本的安装命令&#xff0c;但安装成功后查看版本&#xff0c;仍然是 torch 2.6.0cpu 1. 清理现有 PyTorch 安装 经过探索发现&#xff0c;需要同时卸载 conda 和 pip 安装的 torch。 conda remove pytorch torchvision …

PHP防火墙代码,防火墙,网站防火墙,WAF防火墙,PHP防火墙大全

PHP防火墙代码,防火墙,网站防火墙,WAF防火墙,PHP防火墙大全 资源宝整理分享&#xff1a;https://www.htple.net PHP防火墙&#xff08;作者&#xff1a;悠悠楠杉&#xff09; 验证测试&#xff0c;链接后面加上?verify_cs1后可以自行测试 <?php //复制保存zzwaf.php$we…

使用 Vitis Model Composer 生成 FPGA IP 核

本文将逐步介绍如何使用 Vitis Model Composer 生成 FPGA IP 核&#xff0c;从建模到部署。 在当今快节奏的世界里&#xff0c;技术正以前所未有的速度发展&#xff0c;FPGA 设计也不例外。高级工具层出不穷&#xff0c;加速着开发进程。传统上&#xff0c;FPGA 设计需要使用硬…

BERT、T5、ViT 和 GPT-3 架构概述及代表性应用

BERT、T5、ViT 和 GPT-3 架构概述 1. BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09; 架构特点 基于 Transformer 编码器&#xff1a;BERT 使用多层双向 Transformer 编码器&#xff0c;能够同时捕捉输入序列中每个词的左右上下文信息…

倚光科技:以创新之光,雕琢全球领先光学设计公司

在光学技术飞速发展的当下&#xff0c;每一次突破都可能为众多领域带来变革性的影响。而倚光&#xff08;深圳&#xff09;科技有限公司&#xff0c;作为光学设计公司的一颗璀璨之星&#xff0c;正以其卓越的创新能力和深厚的技术底蕴&#xff0c;引领着光学设计行业的发展潮流…

数据结构(六)——红黑树及模拟实现

目录 前言 红黑树的概念及性质 红黑树的效率 红黑树的结构 红黑树的插入 变色不旋转 单旋变色 双旋变色 插入代码如下所示&#xff1a; 红黑树的查找 红黑树的验证 红黑树代码如下所示&#xff1a; 小结 前言 在前面的文章我们介绍了AVL这一棵完全二叉搜索树&…

解决 Vue 中 input 输入框被赋值后,无法再修改和编辑的问题

目录 需求&#xff1a; 出现 BUG&#xff1a; Bug 代码复现 解决问题&#xff1a; 解决方法1&#xff1a; 解决方法2 关于 $set() 的补充&#xff1a; 需求&#xff1a; 前段时间&#xff0c;接到了一个需求&#xff1a;在选择框中选中某个下拉菜单时&#xff0c;对应的…

【差分隐私相关概念】瑞丽差分隐私(RDP)-瑞丽散度约束了贝叶斯因子后验变化

分步解释和答案&#xff1a; 在Rnyi差分隐私&#xff08;RDP&#xff09;框架中&#xff0c;通过贝叶斯因子和Rnyi散度的关系可以推导出关于后验变化的概率保证。以下是关键步骤的详细解释&#xff1a; 1. 贝叶斯因子的定义与分解 设相邻数据集 D D D 和 D ′ D D′&#x…

前端快速入门——JavaScript函数、DOM

1.JavaScript函数 函数是一段可重复使用的代码块&#xff0c;它接受输入(参数)、执行特定任务&#xff0c;并返回输出。 <scricpt>function add(a,b){return ab;}let cadd(5,10);console.log(c); </script>2.JavaScript事件 JavaScript绑定事件的方法&#xff1…

10【模块学习】LCD1602(二):6路温度显示+实时时钟

项目&#xff1a;6路温度显示实时时钟 1、6路温度显示①TempMenu.c文件的代码②TempMenu.h文件的代码③main.c文件的代码④Timer.c文件的代码⑤Delay.c文件的代码⑥Key.c文件的代码 2、实时时钟显示①BeiJingTime.c文件的代码②BeiJingTime.h文件的代码③main.c文件的代码如下④…

PDF处理控件Aspose.PDF指南:使用 C# 从 PDF 文档中删除页面

需要从 PDF 文档中删除特定页面&#xff1f;本快速指南将向您展示如何仅用几行代码删除不需要的页面。无论您是清理报告、跳过空白页&#xff0c;还是在共享前自定义文档&#xff0c;C# 都能让 PDF 操作变得简单高效。学习如何以编程方式从 PDF 文档中选择和删除特定页面&#…

如何在不同版本的 Elasticsearch 之间以及集群之间迁移数据

作者&#xff1a;来自 Elastic Kofi Bartlett 当你想要升级一个 Elasticsearch 集群时&#xff0c;有时候创建一个新的独立集群并将数据从旧集群迁移到新集群会更容易一些。这让用户能够在不冒任何停机或数据丢失风险的情况下&#xff0c;在新集群上使用所有应用程序测试其所有…

Day08【基于预训练模型分词器实现交互型文本匹配】

基于预训练模型分词器实现交互型文本匹配 目标数据准备参数配置数据处理模型构建主程序测试与评估总结 目标 本文基于预训练模型bert分词器BertTokenizer&#xff0c;将输入的文本以文本对的形式&#xff0c;送入到分词器中得到文本对的词嵌入向量&#xff0c;之后经过若干网络…

npm和npx的作用和区别

npx 和 npm 是 Node.js 生态系统中两个常用的工具&#xff0c;它们有不同的作用和使用场景。 1. npm&#xff08;Node Package Manager&#xff09; 作用&#xff1a; npm 是 Node.js 的包管理工具&#xff0c;主要用于&#xff1a; 安装、卸载、更新项目依赖&#xff08;包&a…

C++学习之金融类安全传输平台项目git

目录 1.知识点概述 2.版本控制工具作用 3.git和SVN 4.git介绍 5.git安装 6.工作区 暂存区 版本库概念 7.本地文件添加到暂存区和提交到版本库 8.文件的修改和还原 9.查看提交的历史版本信息 10.版本差异比较 11.删除文件 12.本地版本管理设置忽略目录 13.远程git仓…