zoukankan      html  css  js  c++  java
  • 单例模式到Java内存模型

    先说单例模式:

    经典的单例模式实现:

    饿汉式:

    public class Singleton {
    
        private static Singleton instance = new Singleton();
        
        public static Singleton getInstance(){
            return instance;
        }
    }

    懒汉式:

    public class Singleton {
    
        private static Singleton instance = null;
        
        synchronized public static Singleton getInstance(){
            if(instance ==null){
                instance = new Singleton();
            }
            return instance;
        }
    }

    这两种都是可以安全运行在多线程下的。但是每一个都有点缺点,对于第一种如果这个单例的初始化需要很多内存和时间,我们希望用到时在初始化,没有用到就不初始化。对于第二种我们,其实只需要在第一次初始化时需要避免线程冲突,其他时候都可以直接返回的,而第二种的实现则变成了完全的串行(因为每一个操作都需要获得对象锁),非常大的降低了并发度。

    我们尝试以下改进:

    一个好的方式是DCL(double-checked locking),这种方式的实现如下:

    public class Singleton {
    
        private static Singleton instance = null;
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null)
                        instance = new Singleton();
                }
            }
            return instance;
        }
    }

    这样看起来是完美的解决方案,确实DCL是一个很好的解决思想,在C下其能很好的运行,但在Java下就会有问题了。这全怪Java的JMM(Java内存模型)。

    在执行到instance = new Singleton()这里时,由于Java内存的“无序写入”,

    可能的执行顺序是这样的:

    mem = allocate();             //Allocate memory for Singleton object.
     instance = mem;               //Note that instance is now non-null, but has not been initialized.
     ctorSingleton(instance);      //Invoke constructor for Singleton passinginstance.

    即为instance分配内存,标记instance不为空,初始化instance。

    这样会导致,一个线程刚标记完,还没有初始化赋值给instance,就释放了锁,然后另一个线程进入锁,判断不为空,释放锁,返回instance,这时显然是错的。这里出现这中错误的原因是instance = new instance();并没有真正的执行完,就释放了锁,我实在不能理解这样设计的原因,但很好的是在JDK1.5之后,已不存在这种问题了DCL这个可以很好的运行。但我们还是有必要继续讨论JDK1.5之前如何实现的。

    可以在instance返回之前加一个步奏,确定其确实初始化了。

    public class Singleton {
    
        private static Singleton instance = null;
    
        public static Singleton getInstance() {
            if (instance == null) {
                Singleton temp = instance;
                synchronized (Singleton.class) {
                    if (temp == null)
                        instance = new Singleton();
                }
                instance = temp;
            }
            return instance;
        }
    }

    这样就很好的解决这个问题了。但是代码量和可阅读性已经陡然上升了,那么有没有更好的方法呢?是有的,利用类加载机制来实现,延迟初始化。

    public class Singleton {
    
        private Singleton() {
    
        }
    
        private static class SingletonHolder {
            private static Singleton instance = new Singleton();
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    }

    这里补充一点类加载的知识,类加载分为一个步骤,加载->验证->准备->解析->初始化->使用->卸载。

    Java中对何时初始化一个类有严格的说明,这里涉及到的一个规则是当类的静态域,或静态方法被引用的时候,必须对声明这个静态域或方法的类进行初始化。至于说明时候对类进行加载,这个有两种形式:饿加载(只要有其他类引用了它就加载),懒加载(初始化的时候才加载)。具体JVM对这点的实现不同。

    所以当懒汉式能保证用到的时候才进行初始化,而饿汉试则是在加载时就初始化了。

  • 相关阅读:
    第一章、Docker 简介
    远程库的创建及操作
    分支
    Git常用命令
    初始化本地仓库
    Git的本地结构与远程中心
    Git的安装
    版本控制系统
    冒泡排序
    选择排序
  • 原文地址:https://www.cnblogs.com/chaiwentao/p/4959648.html
Copyright © 2011-2022 走看看