执行上述代码显示以下五条指令
总结:
当volatile开始修饰一个变量的时候,代表
CPU速度特别快,他比内存快100个数量级,比硬盘快100万。
指令1与指令2两者没有依赖关系,CPU为了提高效率,可能原先执行指令1然后在执行指令2变为先执行指令2在执行指令1,这个就是执行的重排序
。
volatile禁止指令重排序
new一个对象,并且他的构造方法是私有的,他有个public方法,外部调用不能new这个对象,只能调用getInstance方法,返回我们new的对象,这时候调用方法返回的对象都是同一个,同一个引用,下面就是饿汉示单例。
程序改造,能不能让他在使用的时候在开始创建对象,不用提前创建好,防止浪费资源,先判断INSTANCE是否为空,不为空直接拿来用。这个方法不能保证线程安全,在多线程的情况会出现上一个线程还在睡眠状态,下一个线程又开始进行执行创建对象,就会导致所以的对象不是同一个对象。
解决办法,线程同步机制,上锁,线程一致性,如果在getInstance里面还有业务逻辑时候锁的粒度太粗了,所以要把锁的粒度变细
解决办法:把锁的粒度变细,这种方法还是有问题的,第一个线程拿到锁然后线程睡眠了,这时候第二个线程再次判断为null,然后往下面执行创建对象,这时候第二个线程拿到锁进行执行创建对象,会导致创建的对象不是同一个。
最终诞生了DCL Double Check Loading
先判断是否为空,为空进行上锁,再次判断是否为空,依然为空就说明没有一个线程改过,没有线程改动过就进行创建对象
类似于CAS(Compare And Set “比较并交换”)现在有个数据为0一堆线程对这条数据进行递增,如何保证数据的一致性。
先建立与class的关联关系(astore_1是把这个引用值赋值到小t)
后进行调用构造方法(invokespecial 是调用构造方法 把内存中的m原来为0现在变为8)
,先建立关联关系的时候t不为空,它指向new出来一半的初始化对象(半初始化状态)解决问题的关键加volatile 指令重排?
因为volatile禁止指令重排序
总结:
单例模式双重检测为什么要加volatile?
因为指令可能重排,在创建对象的时候应该先调用构造方法在进行引用赋值astore_1,但是由于指令有可能会重排,这两个先后顺序会不一样。
硬件层的并发优化基础知识
CPU Cache 通常分为三级缓存:L1 Cache、L2 Cache、L3 Cache,级别越低的离 CPU 核心越近,访问速度也快,但是存储容量相对就会越小。其中,在多核心的 CPU 里,每个核心都有各自的 L1/L2 Cache,而 L3 Cache 是所有核心共享使用的。
CPU速度特别快,他比内存快100个数量级,比硬盘快100万。
这里会产生一个问题 :数据不一致问题
假如有个数据在我们的main memory L4主存当中,这个数他会被load到L3这个缓存当中,L2与L1这个两级缓存在一个CPU的内部,就会产生一种情况:主存里面的数据会被我们load到不同的CPU内部
例如CPU1把X变为1,CPU2把x变为2就会产生数据不一致的问题
不同的线程或者CPU都是通过总线去访问主存里面的数据,在总线加把锁,当其中一个CPU去访问主存里面的数的时候,其他CPU不允许访问,这样效率偏低,所以总线锁是老的CPU在用。
新的CPU用各种各样一致性协议
简要介绍:
它给每一个缓存里面做了一个标记 四种状态标记:
通过这个协议来让各个CPU之间的缓存保持一致,还是无法解决总线锁,总线锁效率比较低,MESI缓存锁效率比较高,但是有一些无法被缓存的数据、数据特别大、跨越多个缓存的数据还是需要总线锁进行支持,现在CPU的底层的一致性,是通过缓存一致性协议,缓存锁(MESI),总线锁一起执行的
缓存行指的是当我们要把内存里面的数据放到CPU自己的内部缓存里面去,他不会单独把值放到CPU缓存里面
例如 一个值12,它会把12这个值以及后面值一起放到缓存里面,读一个内容把一块的内容全读进去,这个一块内存称为缓存行,多数为64个字节。
public class FalseShareTest implements Runnable {
// 并发线程数:4
public static int NUM_THREADS = 4;
// 迭代次数:100万次
public final static long ITERATIONS = 1_000_000L;
// 数组索引数
private final int arrayIndex;
// VolatileLong对象数组
private static VolatileLong[] longs;
// 花费总时长
public static long SUM_TIME = 0l;
public FalseShareTest(final int arrayIndex) {
this.arrayIndex = arrayIndex;
}
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;
}
}
public final static class VolatileLong {
// 加volatile让变量的修改对所有线程可见
public volatile long value = 0L;
}
public static void main(final String[] args) throws Exception {
// 执行10次
for (int j = 0; j < 10; j++) {
// 构建实验对象数组
longs = new VolatileLong[NUM_THREADS];
for (int i = 0; i < longs.length; i++) {
longs[i] = new VolatileLong();
}
// 开始时间戳
final long start = System.currentTimeMillis();
// 运行测试程序
runTest();
// 结束时间戳
final long end = System.currentTimeMillis();
SUM_TIME += end - start;
}
System.out.println("总耗时:" + SUM_TIME);
}
}
解决办法:
想办法让这个两个对象不为与一个缓存行,位于两个不同的行 (缓存行对齐与填充)
public final static class VolatileLong {
// 填充
public long p1, p2, p3, p4, p5;
// 加volatile让变量的修改对所有线程可见
public volatile long value = 0L;
// 填充
public long p6, p7, p8, p9, p10;
}