上一篇 <<
Volatile的伪共享问题
CPU每次均会以固定长度读取,一般为64bit,导致就算只改了A,也会把其他没改的B-K一起读取,降低了效率
Volatile的伪共享解决办法
/**
* 解决办法:【数据填充】
* JDK6中,定义p1-6,加上value,一共占用56个字节 ,在加上VolatileLong类中头占用8个字节一共就是占用64个字节。
* public final static class VolatileLong{
* public volatile long value = 0L;
* public long p1, p2, p3, p4, p5, p6;
* }
* jdk7中,写个类单独继承方
* public final static class VolatileLong extends AbstractPaddingObject {
* public volatile long value = 0L;
* }
* public class AbstractPaddingObject {
* public long p1, p2, p3, p4, p5, p6;
* }
* jdk8中,使用注解@sun.misc.Contended,启动的时候需要加上该参数-XX:-RestrictContended
* ConcurrentHashMap中就使用了此注解
* @sun.misc.Contended static final class CounterCell {}
*
*/
public class FalseShareTest implements Runnable {
// 定义4和线程
public static int NUM_THREADS = 4;
// 递增+1
public final static long ITERATIONS = 500L * 1000L * 1000L;
private final int arrayIndex;
// 定义一个 VolatileLong数组
private static VolatileLong[] longs;
// 计算时间
public static long SUM_TIME = 0l;
public FalseShareTest(final int arrayIndex) {
this.arrayIndex = arrayIndex;
}
public static void main(final String[] args) throws Exception {
for (int j = 0; j < 10; j++) {
System.out.println(j);
if (args.length == 1) {
NUM_THREADS = Integer.parseInt(args[0]);
}
longs = new VolatileLong[NUM_THREADS];
for (int i = 0; i < longs.length; i++) {
longs[i] = new VolatileLong();
}
final long start = System.nanoTime();
runTest();
final long end = System.nanoTime();
SUM_TIME += end - start;
}
System.out.println("平均耗时:" + SUM_TIME / 10);
}
private static void runTest() throws InterruptedException {
Thread[] threads = new Thread[NUM_THREADS];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new FalseShareTest(i));
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
}
public void run() {
long i = ITERATIONS + 1;
while (0 != --i) {
longs[arrayIndex].value = i;
}
}
@sun.misc.Contended
public final static class VolatileLong {
// extends AbstractPaddingObject
// 8个字节 对象占用8个字节
public volatile long value = 0L;
// public long p1, p2, p3, p4, p5, p6;
// 48+ 64
}
}
Volatile解决重排序问题
- 重排序:编译器和处理器为了提高并行的效率会对代码执行重排序,单线程程序执行结果不会发生改变的,也就是as-ifserial语义,但在多线程情况下就会存在问题。
/**
* thread1和thread2在单线程情况下重排序都没问题,但在多线程下就存在重排序的问题:
* 第856750次(0,1)
* 第856751次(0,1)
* 第856752次(0,0)
* 解决办法:使用volatile或手动插入屏障
* UnSafeUtils.getUnsafe().loadFence()--读屏障
* UnSafeUtils.getUnsafe().storeFence();--写屏障
*
*
**/
public class ReorderThread {
// 全局共享的变量
private /*volatile*/ static int a = 0, b = 0;
private /*volatile*/ static int x = 0, y = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (true) {
i++;
a = 0;
b = 0;
x = 0;
y = 0;
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
//插入一个内存写屏障
UnSafeUtils.getUnsafe().storeFence();
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
//插入一个内存写屏障
UnSafeUtils.getUnsafe().storeFence();
y = a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("第" + i + "次(" + x + "," + y + ")");
if (x == 0 && y == 0) {
break;
}
}
}
}
内存屏障解决重排序
1.写内存屏障:在指令后插入Stroe Barrier ,能够让写入缓存中的最新数据更新写入主内存中,让其他线程可见。
2.读内存屏障:在指令前插入load Barrier ,可以让告诉缓存中的数据失效,强制
读取主内存,让cpu缓存与主内存保持一致,避免缓存导致的一致性问题。
双重检验锁的单例也应该加上volatile
/**
* 双重检验锁的单例也应该加上volatile
*
* new Singleton03()完成的动作:
* 1.分配对象的内存空间memory=allocate();
* 2.调用构造函数初始化
* 3.将对象复制给变量
*
* 第二步和第三步流程存在重排序,将对象复制给变量,在执行调用构造函数初始化,导致另外一个线程获取到该对象不为空,但是该改造函数没有初始化,所以就报错了
*
*/
public class Singleton03 {
private static volatile Singleton03 singleton03;
public static Singleton03 getInstance() {
// 第一次检查
if (singleton03 == null) {
//第二次检查
synchronized (Singleton03.class) {
if (singleton03 == null) {
singleton03 = new Singleton03();
}
}
}
return singleton03;
}
public static void main(String[] args) {
Singleton03 instance1 = Singleton03.getInstance();
Singleton03 instance2 = Singleton03.getInstance();
System.out.println(instance1==instance2);
}
}
Volatile和synchronized区别?
a.Volatile保证线程可见性,当工作内存中副本数据无效之后,主动读取主内存中数据,但是并不能保证原子性,Synchronized是保证线程的原子性
b.Volatile可以禁止重排序的问题,底层使用内存屏障。
c.Volatile不会导致线程阻塞,不能够保证线程安全问题,synchronized 会导致线程阻塞能够保证线程安全问题,执行效率较低。
总体而言volatile关键字在某些情况下性能要优于synchronized
相关文章链接:
<<<多线程基础
<<<线程安全与解决方案
<<<锁的深入化
<<<锁的优化
<<
<<<并发队列
<<