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,优化还是很明显的。