---恢复内容开始---

Ⅰ.饿汉式天生线程安全

Ⅱ.以下是懒汉式单例模式以及思考

public class DoubleCheckedLocking {

    private static volatile DoubleCheckedLocking instance;

    private DoubleCheckedLocking() {
    }

    public static DoubleCheckedLocking getInstance() {

        //已经存在对象,避免不必要的同步
        if (null != instance) {
            return instance;
        }
        synchronized (DoubleCheckedLocking.class) {
            if (null == instance) {
           //3.这里有可能发生指令重排
                instance = new DoubleCheckedLocking();
                //new一个对象 1.开辟空间 2.初始化对象信息 3.返回对象的地址给引用
                //假设new这个对象很耗时(可能存在大量成员变量),可能存在先执行3,再执行2
                //A线程在初始化对象,3步骤先于2执行,先返回引用,假设此时B线程执行if判断,则返回instance,但这里instance并没有完成初始化,在使用中可能会产生NPE异常
            }
        }
        return instance;
    }
}

 

/**
 * 线程安全,懒加载,无锁效率高
 */
public class SingletonObject2 {

    private SingletonObject2() {

    }

    /**
     * static可以保证在jvm中唯一性
     * 需要时才会加载
     * 当线程调用getInstance() class InstanceHolder 会主动加载,并初始化构建instance对象
     * java内存模型保证类初始化是线程安全的
     */
    private static class InstanceHolder {
        private static final SingletonObject2 instance = new SingletonObject2();
    }

    private static SingletonObject2 getInstance() {
        return InstanceHolder.instance;
    }
}

 

使用枚举类特性实现线程安全的单例模式(推荐)

public class SingletonObject3 {

    private enum Singleton {
        INSTANCE;
        private final SingletonObject3 instacne;

        Singleton() {
            instacne = new SingletonObject3();
        }

        public SingletonObject3 getInstacne() {
            return instacne;
        }
    }

    public static SingletonObject3 getInstance() {
        return Singleton.INSTANCE.getInstacne();
    }
    
}

 

---恢复内容结束---

直接看代码

private static int INIT_VALUE = 0;
    private static int MAX_VALUE = 5;
    public static void main(String[] args) {
        //读线程
        new Thread(() -> {
            int localVal = INIT_VALUE;
            while (localVal < MAX_VALUE) {
                //localVal 无法感知到 INIT_VALUE的变化
                if (localVal != INIT_VALUE) {
                    System.out.printf("the value updated to [%d]\n", INIT_VALUE);
                    localVal = INIT_VALUE;
                    INIT_VALUE = localVal;
                }
            }
        }, "READ").start();

        //写线程
        new Thread(() -> {
            int localVal = INIT_VALUE;
            while (localVal < MAX_VALUE) {
                try {
                    System.out.printf("updated value to [%d]\n", ++localVal);
                    INIT_VALUE = localVal;
                    Thread.sleep(150);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "UPDATER").start();

    }

输出结果

updated value to [1]
updated value to [2]
updated value to [3]
updated value to [4]
updated value to [5]

读线程无法感知 INIT_VALUE的变化

 

原因是因为JVM内存模型

 1)所有变量都储存在主内存中

 2) 每个线程都有自己独立的工作内存,里面保存改线程使用到的变量副本(该副本是主内存中该变量的一份拷贝)

 (3)线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接在主内存中读写
 (4)不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
   线程1对共享变量的修改,要想被线程2及时看到,必须经过如下2个过程:
     ①把工作内存1中更新过的共享变量刷新到主内存中
     ②将主内存中最新的共享变量的值更新到工作内存2中

CPU会将内存中的数据存入Cache中(即线程独立的工作内存中),写线程只对“自己”的Cache做修改操作,而读线程无法感知

 

 

现代操作系统对这种问题的解决方案大致可以分为以下两种:

  1.数据总线加锁

    缺点就是多核CPU串行化运行,效率低。

    总线(数据总线,地址总线,控制总线)

  2.cpu高速缓存一致性协议

    1.当cpu写入数据,如果该变量被共享(也就是说在其他cpu中也存在该变量的副本),会发出信号,通知其他cpu该变量缓存无效

    2.当其他cpu访问该变量,重新到主内存中获取