侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 292 篇文章
  • 累计创建 132 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Java并发编程之原子操作类AtomicLong详解

孔子说JAVA
2022-06-04 / 0 评论 / 0 点赞 / 125 阅读 / 5,652 字 / 正在检测是否收录...

并发编程原子类完整教程:Java并发编程之原子操作类实战教程

与 AtomicInteger 非常类似,AtomicLong提供了原子性操作long类型数据的解决方案,AtomicLong 同样也继承自Number类,AtomicLong所提供的原子性方法在使用习惯上也与AtomicInteger非常一致。AtomicLong 可以理解是加了 synchronized 的 long。

1、AtomicLong的使用

public class Counter {  
    private static long counter = 0;  
    public static long addOne(){  
        return ++counter;  
    }  
} 

这个类如果在多线程的环境下会出现问题,假设系统中开了多个线程都来使用这个计数类,它会表现的“不稳定”,具体代码如下:

 public static void main(String[] args) {  
        for(int i=0;i<100;i++){  
            Thread counterThread = new Thread(){  
                @Override  
                public void run() {  
                    try {  
                        Thread.sleep(100);  
                        if(Counter.addOne() == 100){  
                            System.out.println("counter = 100");  
                        }  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                }  
            };  
            counterThread.start();  
        }  
    }  

本例中我们运行了100个线程,每个线程都会把 counter 加一。最终应该有一个线程会拿到 counter = 100 的值,但实际运行情况是大多数据情况下都取不到100,在少数情况下能得到100的值。这是为什么呢?

这是因为 Counter 类在 addOne()方法被调用时,并不能保证线程的安全,即它不是原子级别的运行性(即++操作符是非原子的操作),而是分多个步骤的。

  • 打个比方:线程1首先取到counter 值,比如为10,然后它准备加1,这时候被线程2占用了cpu,它取到counter为10,然后加了1,得到了11。这时线程1 又拿到了CPU资源,继续它的步骤,加1为11,然后也得到了11。这就有问题了。

针对上述问题有解决办法吗?JDK 1.5之后在concurrent包里提供了一些线程安全的基本数据类型的实现,比如 Long 型对应的concurrent包的类是 AtomicLong。我们可以改造代码如下:

import java.util.concurrent.atomic.AtomicLong;  

public class Counter {  
   private static AtomicLong counter = new AtomicLong(0);  

   public static long addOne() {  
     return counter.incrementAndGet();  
   }  
}  

我们可以反复执行上述代码,结果都是输出 counter = 100。所以在多线程环境下,可以简单使用 AtomicXXX 类使代码变得线程安全。

2、AtomicLong方法详解

2.1 构造方法详解

public AtomicLong(long initialValue):创建具有给定初始值的新 AtomicLong。

  • 参数:initialValue - 初始值

public AtomicLong():创建具有初始值 0 的新 AtomicLong。

2.2 get()

public final long get():获取当前值。

2.2 set(long newValue)

public final void set(long newValue):设置为给定值。

  • 参数:newValue - 新值

2.3 lazySet

public final void lazySet(long newValue):最后设置为给定值。

  • 参数:newValue - 新值
  • 从1.6版本开始支持

2.4 getAndSet

public final long getAndSet(long newValue):以原子方式设置为给定值,并返回旧值。

  • 参数:newValue - 新值
  • 返回:以前的值

2.5 compareAndSet

public final boolean compareAndSet(long expect, long update):如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

  • 参数:expect - 预期值
  • 参数:update - 新值
  • 返回:如果成功,则返回 true。返回 false 指示实际值与预期值不相等。

2.6 weakCompareAndSet

public final boolean weakCompareAndSet(long expect, long update):如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。可能意外失败并且不提供排序保证,所以只能在很少的情况下对 compareAndSet 进行适当地选择。

  • 参数:expect - 预期值
  • 参数:update - 新值
  • 返回:如果成功,则返回 true。

2.7 getAndIncrement

public final long getAndIncrement():以原子方式将当前值加 1。

  • 返回:以前的值

2.7 getAndDecrement

public final long getAndDecrement():以原子方式将当前值减 1。

  • 返回:以前的值

2.8 getAndAdd

public final long getAndAdd(long delta):以原子方式将给定值添加到当前值。

  • 参数:delta - 要添加的值
  • 返回:以前的值

2.9 incrementAndGet

public final long incrementAndGet():以原子方式将当前值加 1。

  • 返回:更新的值

2.10 decrementAndGet

public final long decrementAndGet():以原子方式将当前值减 1。

  • 返回:更新的值

2.11 addAndGet

public final long addAndGet(long delta):以原子方式将给定值添加到当前值。

  • 参数:delta - 要添加的值
  • 返回:更新的值

2.12 toString

public String toString():返回当前值的字符串表示形式。覆盖了类 Object 中的 toString方法。

  • 返回:当前值的字符串表示形式。

2.13 intValue

public int intValue():从类 Number 复制的描述,以 int 形式返回指定的数值。这可能会涉及到舍入或取整。类 Number 中的 intValue。

  • 返回:转换为 int 类型后该对象表示的数值。

2.14 longValue

public long longValue():从类 Number 复制的描述,以 long 形式返回指定的数值。这可能涉及到舍入或取整。类 Number 中的 longValue。

  • 返回:转换为 long 类型后该对象表示的数值。

2.15 floatValue

public float floatValue():从类 Number 复制的描述,以 float 形式返回指定的数值。这可能会涉及到舍入。类 Number 中的 floatValue。

  • 返回:转换为 float 类型后该对象表示的数值。

2.16 doubleValue

public double doubleValue():从类 Number 复制的描述,以 double 形式返回指定的数值。这可能会涉及到舍入。类 Number 中的 doubleValue。

  • 返回:转换为 double 类型后该对象表示的数值。

3、compareAndSwapLong和compareAndSwapInt

AtomicInteger类中最为关键的方法为compareAndSwapInt,同样,在AtomicLong类中也提供了类似的方法compareAndSwapLong,但是该方法要比compareAndSwapInt复杂很多。

相对于compareAndSwapInt方法,在unsafe.cpp中,compareAndSwapLong方法多了条件编译SUPPORTS_NATIVE_CX8。SUPPORTS_NATIVE_CX8主要用于判断机器硬件是否支持8字节数字的cmpxchg CPU指令,如果机器硬件不支持,比如32位的CPU肯定不支持8字节64位数字的cmpxchg CPU指令,那么此时就需要判断当前JVM版本是否支持8字节数字的cmpxchg操作;如果机器硬件与当前JVM的版本都不支持,那么实际上针对long型数据的原子性操作将不会是Lock Free的,而是需要采用加锁的方式确保原子性。

通过下面的代码,我们将会看到自己机器上安装的JDK是否支持8字节数字(长整型)的Lock Free CAS操作。

 public static void main(String[] args)
            throws NoSuchFieldException, IllegalAccessException {
        Field vm_supports_long_cas = AtomicLong.class.getDeclaredField("VM_SUPPORTS_LONG_CAS");
        vm_supports_long_cas.setAccessible(true);
        boolean isSupport = (boolean) vm_supports_long_cas.get(null);
        System.out.println(isSupport);
    }

AtomicLong使用总结

AtomicLong 的使用跟 AtomicInteger 使用方式是一样的,但是有一点需要注意的是,在编译器编译成字节码时,会先判断当前JVM 或者 机器硬件是否支持 8字节的Lock Free CAS操作,如果支持则通过 Free Lock 操作,如果不支持则会通过加锁处理(由synchronized关键字来承担)。

4、LongAdder(累加器)比AtomicLong原子操作效率比较

在高并发情况下,LongAdder(累加器)比AtomicLong原子操作效率更高,LongAdder累加器是java8新加入的,参考以下压测代码:

AtomicLong测试代码

/**
 * @description 压测AtomicLong的原子操作性能
 **/
public class AtomicLongTest implements Runnable {
 
    private static AtomicLong atomicLong = new AtomicLong(0);
 
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            atomicLong.incrementAndGet();
        }
    }
 
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(30);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            es.submit(new AtomicLongTest());
        }
        es.shutdown();
        //保证任务全部执行完
        while (!es.isTerminated()) { }
        long end = System.currentTimeMillis();
        System.out.println("AtomicLong add 耗时=" + (end - start));
        System.out.println("AtomicLong add result=" + atomicLong.get());
    }
}

LongAdder测试代码

/**
 * @description 压测LongAdder的原子操作性能
 **/
public class LongAdderTest implements Runnable {
 
    private static LongAdder longAdder = new LongAdder();
 
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            longAdder.increment();
        }
    }
 
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(30);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            es.submit(new LongAdderTest());
        }
        es.shutdown();
        //保证任务全部执行完
        while (!es.isTerminated()) {
        }
        long end = System.currentTimeMillis();
        System.out.println("LongAdder add 耗时=" + (end - start));
        System.out.println("LongAdder add result=" + longAdder.sum());
    }
}

在高并发竞争情形下,AtomicLong每次进行add都需要flush和refresh(这一块涉及到java内存模型中的工作内存和主内存的,所有变量操作只能在工作内存中进行,然后写回主内存,其它线程再次读取新值),每次add()都需要同步,在高并发时会有比较多冲突,比较耗时导致效率低;而LongAdder中每个线程会维护自己的一个计数器,在最后执行LongAdder.sum()方法时候才需要同步,把所有计数器全部加起来,不需要flush和refresh操作。

0

评论区