找兼职做网站的哪里找,做网站 多页面网址怎么弄,全网热度指数,公司注册成本“不积跬步#xff0c;无以至千里。” 引言
在并发编程中#xff0c;伪共享#xff08;False Sharing#xff09;是一种性能问题#xff0c;特别是在多核处理器上。这个问题通常出现在多个线程同时修改彼此不同但共享同一缓存行的数据。为了解决伪共享问题#xff0c;我… “不积跬步无以至千里。” 引言
在并发编程中伪共享False Sharing是一种性能问题特别是在多核处理器上。这个问题通常出现在多个线程同时修改彼此不同但共享同一缓存行的数据。为了解决伪共享问题我们需要采用一些技术手段特别是在Java中使用合适的填充技术可以有效提高性能。
什么是伪共享
伪共享是由于缓存行的概念引起的。现代计算机架构中缓存被分割成一些大小固定的缓存行通常为64字节。多个线程可能在不同的核上同时访问同一缓存行的不同部分这导致了缓存行的频繁无效化和刷新从而降低了性能。
为什么伪共享是一个问题
伪共享导致了缓存行的频繁无效化和刷新从而增加了线程之间的通信开销降低了程序的性能。在多核处理器上不同核的缓存可能同时包含相同缓存行的不同部分当一个核修改了其中一个部分时其他核不得不无效化整个缓存行即使它们修改的是不同的变量。这就是伪共享问题的本质。
Java中的填充技术
1. Volatile修饰符
在Java中我们可以使用volatile关键字修饰共享的变量以确保线程之间的可见性。volatile不仅会防止编译器进行指令重排序还会禁止缓存的使用从而避免了伪共享问题。
volatile关键字在Java中用于确保多线程之间的可见性。它的工作原理是禁止线程对被volatile修饰的变量进行本地缓存每次访问该变量时都会从主内存中重新读取。这样可以防止线程读取过期的值从而避免了伪共享问题。
然而volatile并不总是适用于所有场景。它的代价是性能相对较低因为它需要频繁地从主内存中读取变量的值。在某些高性能要求的场景下使用其他技术可能更为合适。
public class VolatileExample {private volatile long sharedValue;// 省略其他代码public long getSharedValue() {return sharedValue;}public void setSharedValue(long value) {this.sharedValue value;}
}2. 缓存行填充
为了避免多个共享变量被放置在同一缓存行中我们可以在它们之间插入一些无用的填充变量使它们在不同的缓存行中。
缓存行填充的原理是在共享变量之间插入一些无用的填充变量使它们位于不同的缓存行中。这样可以确保每个线程修改自己的变量时不会影响其他线程的变量从而避免了缓存行的频繁无效化和刷新。
在示例中我们添加了一些padding变量这些变量不参与实际的业务逻辑只是为了填充缓存行。这样即使多个线程同时访问不同的变量它们仍然位于不同的缓存行中不会发生伪共享问题。
public class PaddedExample {private long sharedValue;// 避免伪共享添加填充变量private long padding1, padding2, padding3, padding4, padding5, padding6, padding7;// 省略其他代码public long getSharedValue() {return sharedValue;}public void setSharedValue(long value) {this.sharedValue value;}
}3. 使用Contended注解
从Java 8开始JVM引入了Contended注解用于告诉JVM在变量的周围插入填充。需要注意的是为了启用Contended的效果需要在JVM启动时添加参数-XX:-RestrictContended。
Contended注解是JVM提供的一种消除伪共享的手段。通过在变量上添加该注解JVM会在变量周围插入填充以确保不同变量位于不同的缓存行中。
需要注意的是为了启用Contended的效果需要在JVM启动时添加参数-XX:-RestrictContended。这是因为在某些情况下默认情况下JVM可能会禁用Contended的效果以提高性能。
import java.util.concurrent.atomic.AtomicLong;public class ContendedExample {sun.misc.Contended(group1)private long sharedValue;// 省略其他代码public long getSharedValue() {return sharedValue;}public void setSharedValue(long value) {this.sharedValue value;}
}示例与最佳实践
为了演示上述技术的使用考虑一个多线程更新共享计数器的场景。我们将使用AtomicLong作为计数器并比较不同技术的性能。
import java.util.concurrent.atomic.AtomicLong;public class SharedCounter {private volatile long volatileCounter;private long paddedCounter;private long contendedCounter;public SharedCounter() {volatileCounter 0;paddedCounter 0;contendedCounter 0;}// 示例中的其他方法public void incrementVolatile() {volatileCounter;}public long getVolatileCounter() {return volatileCounter;}public void incrementPadded() {paddedCounter;}public long getPaddedCounter() {return paddedCounter;}public void incrementContended() {contendedCounter;}public long getContendedCounter() {return contendedCounter;}
}通过这个示例我们可以使用不同的技术来更新计数器并比较它们的性能。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args) throws InterruptedException {SharedCounter sharedCounter new SharedCounter();ExecutorService executorService Executors.newFixedThreadPool(3);// 使用volatileexecutorService.submit(() - {for (int i 0; i 1_000_000; i) {sharedCounter.incrementVolatile();}});// 使用填充executorService.submit(() - {for (int i 0; i 1_000_000; i) {sharedCounter.incrementPadded();}});// 使用ContendedexecutorService.submit(() - {for (int i 0; i 1_000_000; i) {sharedCounter.incrementContended();}});executorService.shutdown();executorService.awaitTermination(1, TimeUnit.MINUTES);System.out.println(Volatile Counter: sharedCounter.getVolatileCounter());System.out.println(Padded Counter: sharedCounter.getPaddedCounter());System.out.println(Contended Counter: sharedCounter.getContendedCounter());}
}性能比较和最佳实践
在实际应用中选择合适的技术取决于具体的需求和场景。以下是一些建议
如果可见性是主要关注点而性能相对较次要可以考虑使用volatile关键字。如果性能是关键问题可以使用缓存行填充技术。在添加填充变量时需要根据实际硬件架构调整填充的字节数以确保变量位于不同的缓存行。如果你使用的是Java 8及以上并且JVM支持Contended注解可以尝试使用该注解。但请注意启用Contended可能会增加内存消耗因此需要在内存和性能之间进行权衡。在实际应用中最佳实践是通过性能测试来评估不同技术的效果。使用性能分析工具和测量工具例如JMHJava Microbenchmarking Harness来获取详细的性能数据。
综上所述消除伪共享是一个需要综合考虑可维护性和性能的问题。选择合适的技术取决于具体的应用场景和需求通过权衡不同技术的优缺点可以找到最适合的解决方案。
结论
通过使用volatile、缓存行填充和Contended等技术我们可以有效地消除伪共享问题提高并发程序的性能。在选择哪种技术时需要根据具体的应用场景和性能要求进行权衡。最佳实践是根据具体情况进行性能测试以确定哪种技术最适合你的应用。在实际开发中根据具体情况选择最适合的优化手段以确保高性能和可维护性的平衡。