---恢复内容开始---
Ⅰ.饿汉式天生线程安全
Ⅱ.以下是懒汉式单例模式以及思考
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访问该变量,重新到主内存中获取