并发专题java

CPU

并发专题java_第1张图片

  1. 现代CPU为了提升执行效率,减少CPU与内存的交互(交互影响CPU效率),一般在CPU上集成了多级缓存架构,常见的为三级缓存结构
L1 Cache,分为数据缓存和指令缓存,逻辑核独占
L2 Cache,物理核独占,逻辑核共享
L3 Cache,所有物理核共享

并发专题java_第2张图片

  1. 存储器存储空间大小:内存>L3>L2>L1>寄存器; 存储器速度快
  2. 慢排序:寄存器>L1>L2>L3>内存;
  3. 还有一点值得注意的是:缓存是由最小的存储区块-缓存行(cacheline)组成,缓存行大小通常为64byte。
  4. 缓存行是什么意思呢?
  5. 比如你的L1缓存大小是512kb,而cacheline = 64byte,那么就是L1里有512 * 1024/64个cacheline

以下代码证明cpu的性能验证

public class testUntil {

    private static final int RUNS = 100;
    private static final int DIMENSION_1 = 1024 * 1024;
    private static final int DIMENSION_2 = 6;
    private static long[][] longs;

    public static void main(String[] args) throws Exception {
         /*初始化数组*/
        longs = new long[DIMENSION_1][];
        for (int i = 0; i < DIMENSION_1; i++) {
            longs[i] = new long[DIMENSION_2];
            for (int j = 0; j < DIMENSION_2; j++) {
                longs[i][j] = 1L;
            }
        }
        System.out.println("Array初始化完毕");
        long sum = 0L;
        long start = System.currentTimeMillis();
        for (int i = 0; i < DIMENSION_1; i++) {
            for (int j = 0; j < DIMENSION_2; j++) {//6
                sum += longs[i][j];
            }
        }
        System.out.println("spend time1:" + (System.currentTimeMillis() - start));
        System.out.println("sum1:" + sum);
        sum = 0L;
        start = System.currentTimeMillis();
        for (int j = 0; j < DIMENSION_2; j++) {//6
            for (int i = 0; i < DIMENSION_1; i++) {//1024*1024
                sum += longs[i][j];
            }
        }
        System.out.println("spend time2:" + (System.currentTimeMillis() - start));
        System.out.println("sum2:" + sum);
    }
}

结果

Array初始化完毕
spend time1:14 //cpu是一组数据横向读取的
sum1:6291456
spend time2:48
sum2:6291456

一下代码证明了volatile的可见性

public class test {

    public volatile static int i = 1;

    public static void increase() {
        i++;
        System.out.println("输出i++:" + i);
        //逻辑核的核数
        Runtime.getRuntime().availableProcessors();
    }
    volatile boolean initFlag = false;

    public void save() {
        this.initFlag = true;
        final String name = Thread.currentThread().getName();
        System.out.println("线程:" + name + "修改共享变量initFlag");
    }

    public void load() {
        final String name = Thread.currentThread().getName();
        while (!initFlag) {
            //线程此处跑空,等待initFlag修改了状态
            System.out.println("此处跑空线程");
        }
        System.out.println("线程:" + name + "当前线程嗅到了initFlag改变状态");
    }

    public static void main(String[] args) {
        test test = new test();
        Thread threadA = new Thread(() -> {
            test.save();
            increase();
        }, "threadA");

        Thread threadB = new Thread(() -> {
            test.load();
            increase();
        }, "threadB");

        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadA.start();
    }
}

结果
此处跑空线程
此处跑空线程
此处跑空线程
线程:threadB当前线程嗅到了initFlag改变状态
线程:threadA修改共享变量initFlag
输出i++:3
输出i++:2
//结论,不加volatile 也可以得到上面的结果,但是volatile 可以让其他线程实时更新变量

以下代码证明了volatile不能保证线程的原子性

public class Jmm04 {

    private volatile static int counter = 0;

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter++;
                }
            });
            thread.start();//新建10个线程并开启
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("counter:" + counter);
    }
}

运行结果
counter:9535(并没有得到一万)

以下代码证明了volatile可以防止指令重排

public class Jmm05 {

   private volatile static  int x = 0,y = 0;//如果不加volatile ,就是用UnsafeInstance
   private volatile static  int a = 0, b= 0;

    public static void main(String[] args)  throws  InterruptedException{
        int i = 0;
        for (;;){
            i++;
             x = 0;y = 0;
             a = 0; b= 0;
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    shortWait(10000);
                    a = 1;
                    //如果不加volatile,怎么进行手动加内存屏障,防止指令重排呢?
                    //有volatile则不需要以下这行代码 
                    UnsafeInstance.reflectGetUnsafe().fullFence();
                    x = b;
                }
            });
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    //有volatile则不需要以下这行代码 
                    UnsafeInstance.reflectGetUnsafe().fullFence();
                    y = a;
                }
            });
            thread1.start();
            thread2.start();
            thread1.join();//等待线程1运行完再继续运行
            thread2.join();
            String result = "第"+i+"次("+x+","+y+")";
            if (x==0 && y==0){
                System.out.println("打印输出:"+result);
                break;
            }else {
                System.out.println("log:"+result);
            }
        }
    }

    public static void shortWait(long interval){
        long start  = System.nanoTime();
        long end;
        do {
            end = System.nanoTime();
        }while (start+interval>=end);
    }
}

UnsafeInstance是1.7版本以后的防止指令重排的工具类

/**
 * 
 * 自动加屏障
 *
 */

public class UnsafeInstance {

    public static Unsafe reflectGetUnsafe() {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            return (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

以下代码证明,指令重拍的严重性

public class DoubleCheckLock {

    private volatile static DoubleCheckLock instance;//懒汉模式
    public static DoubleCheckLock getInstance() {
        if (instance == null) {
            //同步对象
            synchronized (DoubleCheckLock.class) {
                //禁止重复创建
                if (instance == null) {
                    //确定创建
                    instance = new DoubleCheckLock();
                }
            }
        }
        return instance;
    }
}
如果指令重排了,其他线程就会出错,抛出异常
1memory = allocate();//1.分配对象内存空间
2instance(memory);//2.初始化对象
3 instance = memory;//3.设置instance指向刚分配的内存地址,此时instanc e!=null
如果指令重排
1memory=allocate();//1.分配对象内存空间
2instance=memory;//3.设置instance指向刚分配的内存地址,此时instanc e!=null,但是对象还没有初始化完成!
3instance(memory);//2.初始化对象
第一个操作 第二个操作:普通读写 第二个操作:volatile读 第二个操作:volatile写
普通读写 可以重排 可以重排 不可以重排
volatile读 不可以重排 不可以重排 不可以重排
volatile写 可以重排 不可以重排 不可以重排

以下是关于表格证明的代码
以上是当只加了其中一个作为volatile 时,是否会指令重排

public class Jmm06 {

    int a = 0;
    private volatile int m1 = 1;
    private volatile int m2 = 2;

    public void readAndwrite() {
        int i = m1;//第一个volatile读
        int j = m2;//第二个volatile读

        int c = 2;//普通写
        m1 = 2;//volatile写 (c与m1此时不会重排)

        a = i + j;//第一个普通写(可以重排)(这里涉及了两个volatile和一个普通的写)

        m1 = i + 1;//第一个volatile写
        m2 = j + 2;//第二个volatile写
    }
}

你可能感兴趣的:(java,缓存,开发语言)