1. 使用AtomicInteger原子类尝试优化分析

Java的java.util.concurrent.atomic包提供了一些原子类,可以在并发编程中避免显式加锁。最简单的我们可以使用AtomicInteger来替代显式的锁。


package org.zyf.javabasic.thread.lock.opti;

import java.util.concurrent.atomic.AtomicInteger;

/**

* @program: zyfboot-javabasic

* @description: 使用AtomicInteger来替代显式的锁

* @author: zhangyanfeng

* @create: 2024-06-05 23:07

**/

public class AtomicExample {

   private static AtomicInteger counter = new AtomicInteger(0);

   public static void main(String[] args) throws InterruptedException {

       long startTime = System.currentTimeMillis();

       Thread[] threads = new Thread[100];

       for (int i = 0; i < 100; i++) {

           threads[i] = new Thread(new IncrementAtomic());

           threads[i].start();

       }

       for (Thread thread : threads) {

           thread.join();

       }

       long endTime = System.currentTimeMillis();

       System.out.println("Time with AtomicInteger: " + (endTime - startTime) + " ms");

   }

   static class IncrementAtomic implements Runnable {

       @Override

       public void run() {

           for (int i = 0; i < 1000000; i++) {

               counter.incrementAndGet();

           }

       }

   }

}

理论上这样优化后性能上必会上升,但实际上运行后其耗时为 6714 ms,性能反而变差了。这里其实我们之前的博客中也有讲过,其主要的原因有两个:


原子类的开销:AtomicInteger的incrementAndGet方法虽然是无锁的,但它依赖于底层的CAS(Compare-And-Swap)操作。CAS操作虽然是无锁的,但在高并发情况下,多个线程同时尝试更新同一个变量时,CAS操作可能会频繁地失败并重试,从而导致性能下降。相比之下,ReentrantLock在某些情况下可能反而表现更好,尤其是在锁争用不是特别激烈的时候。

高并发下的内存争用:在高并发情况下,多个线程同时访问和修改共享变量会导致内存争用。这种争用在使用AtomicInteger时表现得更为明显,因为每次操作都需要与主内存同步,可能会导致缓存一致性协议的开销。

2. 对AtomicInteger原子类进一步优化

我们可以尝试以下方法来进一步优化:


减少线程数量:在本示例中,我们使用了100个线程同时访问共享变量,这可能导致过多的上下文切换和争用。可以尝试减少线程数量,看看性能是否有所改善。

使用更高效的同步机制:可以尝试使用其他同步机制,如LongAdder或ConcurrentLinkedQueue等,这些工具在高并发场景下通常表现更好。

这里我们直接用LongAdder验证,具体代码如下:


package org.zyf.javabasic.thread.lock.opti;

import java.util.concurrent.atomic.LongAdder;

/**

* @program: zyfboot-javabasic

* @description: LongAdder在高并发情况下比AtomicInteger有更好的性能

* @author: zhangyanfeng

* @create: 2024-06-05 23:26

**/

public class LongAdderExample {

   private static LongAdder counter = new LongAdder();

   public static void main(String[] args) throws InterruptedException {

       long startTime = System.currentTimeMillis();

       Thread[] threads = new Thread[100];

       for (int i = 0; i < 100; i++) {

           threads[i] = new Thread(new IncrementLongAdder());

           threads[i].start();

       }

       for (Thread thread : threads) {

           thread.join();

       }

       long endTime = System.currentTimeMillis();

       System.out.println("Time with LongAdder: " + (endTime - startTime) + " ms");

   }

   static class IncrementLongAdder implements Runnable {

       @Override

       public void run() {

           for (int i = 0; i < 1000000; i++) {

               counter.increment();

           }

       }

   }

}

运行后发现这个时候的耗时基本在204 ms,优化还是很明显的。