Java代码规范之使用volatile解决多线程内存可见性问题
目录
该条规范是什么
该规范指出在Java编程中,使用volatile
关键字可以解决多线程内存不可见性问题。对于一写多读的情况,volatile
可以保证变量的同步性。但是对于多写的情况,volatile
无法解决线程安全问题。
为什么这么规定
以下是该规范的原因:
- 内存可见性问题:在多线程环境下,如果没有合适的同步机制,一个线程对共享变量的修改可能对其他线程是不可见的,即内存可见性问题。通过使用
volatile
关键字可以确保变量在不同线程间的可见性。 - 原子性问题:对于一些复合操作(如
count++
),单纯使用volatile
关键字并不能保证线程安全。此时可以使用AtomicInteger
类来保证原子性操作,或者在JDK8及以上版本中推荐使用LongAdder
对象,它比AtomicLong
在性能上更好(减少乐观锁的重试次数)。
多种主要用法及其代码示例
使用volatile关键字保证变量可见性
public class Example {
private volatile int count;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
使用AtomicInteger实现线程安全的自增操作
import java.util.concurrent.atomic.AtomicInteger;
public class Example {
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.addAndGet(1);
}
public int getCount() {
return count.get();
}
}
使用LongAdder实现线程安全的自增操作(适用于JDK8及以上版本)
import java.util.concurrent.atomic.LongAdder;
public class Example {
private LongAdder count = new LongAdder();
public void increment() {
count.increment();
}
public long getCount() {
return count.longValue();
}
}
LongAdder 原理示例
LongAdder
是 Java 中 java.util.concurrent.atomic
包下的一个原子类,它用于高效地实现多线程环境下的长整型累加操作。相比于传统的原子类如 AtomicLong
,LongAdder
在高并发场景下具有更好的性能。
原理
LongAdder
的原理是采用分段锁(Striped Locking)的方式,将计数器分成多个小计数器,每个线程对应一个小计数器。在进行累加操作时,不同线程会独立地更新各自的小计数器,最后再将所有小计数器的值求和,从而得到最终的累加结果。
这种方式可以减少并发竞争,降低了线程之间的争用,提升了并发性能。
示例
以下是一个使用 LongAdder
的示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
public class Example {
private static final int NUM_THREADS = 10;
private static final int NUM_INCREMENTS = 100000;
public static void main(String[] args) throws InterruptedException {
LongAdder counter = new LongAdder();
ExecutorService executorService = Executors.newFixedThreadPool(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
executorService.execute(() -> {
for (int j = 0; j < NUM_INCREMENTS; j++) {
counter.increment();
}
});
}
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES);
System.out.println("Counter: " + counter.sum());
}
}
在上述示例中,首先创建了一个 LongAdder
对象 counter
。然后创建了一个固定大小的线程池,并提交了多个任务,每个任务会对计数器进行一定次数的自增操作。最后等待线程池中的任务执行完毕,并输出最终的累加结果。
通过使用 LongAdder
,多个线程可以并发地对计数器进行自增操作,而无需竞争全局锁,从而提升了并发性能。
需要注意的是,LongAdder
在累加结果时使用了延迟初始化,因此在调用 sum()
方法时才会真正计算出所有小计数器的累加结果。
LongAdder
还提供了其他一些方法,如 increment()
, decrement()
, add()
, sumThenReset()
等,可以根据具体需求选择适当的方法。
LongAdder
是在 Java 8 中引入的,用于解决高并发场景下原子累加操作的性能问题。它可以作为一个替代方案来改善原子长整型操作的性能。