栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

为什么double check lock需要加volatile

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

为什么double check lock需要加volatile


进程:一个程序运行起来放入内存中
进程是静态的,一个程序为它分配资源的基本单位
线程是动态的,是执行cpu调度的基本单位

cpu:给我什么指令执行什么指令(会有线程之间的切换,要记录线程执行的程度)
PC
ALU:计算(1+2)
Register:程序计数器

单核cpu为什么有必要考虑多线程
未必所有时间都在做计算(如果一半时间在等待,如果这时间别人不能用就浪费了cpu的资源)
线程池核心线程数设置多少合适
充分利用cpu,一半计算一半等待(2个线程合适)
8核cpu,只有1/4的时间在计算
8480%


4核8线程就是1个ALU 服务2个寄存器

缓存一致性协议(inter 用的是 MESI,不同cpu用的不同的协议)

import java.util.concurrent.CountDownLatch;

public class CacheLinePadding {
    public static long COUNT = 1000000000L;

    private static class T {
    //观察打开前打开后
        //private long p1, p2, p3,p4, p5, p6, p7;
        public long x = 0L; //8bytes
        //private Long p9,p10, p11, p12, p13, p14, p15;
    }

    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws Exception {
        CountDownLatch latch = new CountDownLatch(2);
        Thread t1 = new Thread(() -> {
            for (long i = 0; i < COUNT; i++) {
                arr[0].x = i;
            }
            latch.countDown();
        });
        Thread t2 = new Thread(() -> {
            for (long i = 0; i < COUNT; i++) {
                arr[1].x = i;
            }
            latch.countDown();
        });
        final long start = System.nanoTime();
        t1.start();
        t2.start();
        latch.await();
        System.out.println((System.nanoTime() - start) / 1000000);

    }
}


cpu乱序

import java.util.concurrent.CountDownLatch;

public class Disorder {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        for (long i = 0; i < Long.MAX_VALUE; i++) {
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            CountDownLatch latch = new CountDownLatch(2);
            Thread one = new Thread(new Runnable() {
                public void run() {
                    a = 1;
                    x = b;
                    latch.countDown();
                }
            });
            Thread other = new Thread(new Runnable() {
                public void run() {
                    b = 1;
                    y = a;
                    latch.countDown();
                }
            });
            one.start();
            other.start();
            latch.await();
            String result = "第" + i + "次(" + x + "," + y + ")";
            if (x == 0 && y == 0) {
                System.err.println(result);
                break;
            }
        }

    }
}

第426291次(0,0)

public class NoVisibility {
    private static boolean ready = false;
    private static int number;

    private static class ReaderThread extends Thread {
        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println(number);
        }
    }

    public static void main(String[] args) throws Exception {
        Thread t = new ReaderThread();
        t.start();
        number = 42;
        ready = true;
        t.join();
    }
}


new 在内存中分配一块内存空间
dup即英文duplicate的缩写,重复的意思,用来定义重复的字节
特殊方法 m=8
astore_1 t关联内存中的值
return

关于Object o = new Object();
1.请解释一下对象的创建过程? (半初始化)
2.加问DCL要不要加volatile问题? (指令重排)
3.对象在内存中的存储布局?(对象与数组的存储不同)
4.对象头具体包括什么? (markword klas spointer) synchronized锁信息
5.对象怎么定位? (直接间接)
6.对象怎么分配? (栈上-线程本地- Eden-0ld)
7. Object o= new Object()在内存中占用多少字节?
8。新问题:为什么hotspot不使用c++上对象来代表java对象?
9.新问题: Class对象是在堆还是在方法区?
public class Mgr05 {
    private static Mgr05 INSTANCE;

    private Mgr05() {
    }

    public static Mgr05 getInstance() {
        if (INSTANCE == null) {
	//妄图通过减小同步代码块的方式提高效率,然后不可行
            synchronized (Mgr05.class) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Mgr05();
            }
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(Mgr05.getInstance().hashCode());
            }).start();
        }
    }
}

因为涉及对象的半初始化所以需要加volatile关键字

public class Mgr06 {
    private static volatile Mgr06 INSTANCE;

    private Mgr06() {
    }

    public static Mgr06 getInstance() {
    //这个if只是为了避免抢锁提高效率
        if (INSTANCE == null) {
            synchronized (Mgr06.class) {
                if (INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(Mgr06.getInstance().hashCode());
            }).start();
        }
    }
}

double check lock(DCL)



单线程保证结果最终一致性就可以乱序
原子性,有序性,可见性

volatile本质

双重检测机制单例代码:

public class Singleton {
 
    private static volatile Singleton singleton;
 
    private Singleton() {}
 
    public static Singleton getInstance() {
        if (singleton == null) {                  1
            synchronized (Singleton.class) {
                if (singleton == null) {          2
                    singleton = new Singleton();  3
                }
            }
        }
        return singleton;
    }
}

了解这个机制之前我们要知道一个定义指令重排:简单理解就是,虚拟机或者CPU会在不影响代码执行结果的情况下,改变代码的执行顺序来优化代码(单线程或正常同步情况下)

singleton = new Singleton()创建对象的过程不是原子性的分为三步:

①.分配对象的内存空间

②.初始化对象

③.将实例指向刚分配的内存地址

但在实际的情况因为指令重排 执行的顺序可能不是①》②》③,而是①》③》②

这就有可能会出现一个问题 : 线程A进入执行代码执行创建代码时执行了①》③时,线程B进入执行代码因为③已经执行了,singleton已经有了内存地址不是null了,所以if不成立直接返回singleton对象,但是由于还没有②初始化对象就会出现错误

为了解决这个问题要使用volatile

volatile主要有两个作用:①可见性 ②有序性

有序性顾名思义用来保证代码顺序执行,告诉虚拟机和CPU这段代码你必须按照顺序执行,不需要你优化。

这样就保证了创建对象必须按照①》②》③来执行,就不会再出现上述的问题了

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/389191.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号