java单例-积木系列

一步步知识点归纳吧,把以前似懂非懂,了解表面,知道点不知道面的知识归一下档。

懒汉式单例:

私有化构造函数,阻止外界实例话对象,调用getInstance静态方法,判断是否已经实例化。

为什么是懒汉,因为它是属于延迟加载这个实例的,也就是说不用到的时候,不实例化对象的。

public class Singleton {
   private static Singleton instance;
   private Singleton (){}

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

}

线程安全问题:当多线程同时调用getInstance()方法,同时判断出instance,进入实例化操作,单利就不复存在。

为了线程安全,那我们对getInstance()方法进行同步化:

public class Singleton {
  private static Singleton instance;
  private Singleton (){}

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

synchronized修饰保证同一时间只有一个线程可以执行getInstance方法,如此可以保证单例的线程安全。但是同步粒度似乎太大,事实上当实例初始化后我们并不需保证一个个线程排队来取这个实例。

那么就引出来双重检验锁的代码:

public class Singleton {
  private static Singleton instance;
  private Singleton (){}

  public static Singleton getSingleton() {
      if (instance == null) {                        //Single Checked
          synchronized (this) {
              if (instance == null) {                //Double Checked
                  instance = new Singleton();
              }
          }
      }
      return instance ;
  }
}

同步快外面和里面check两次,保证在进入同步块的线程不产生新的实例。

当多个线程并发时都判断出实例为null则开始竞争锁,其中一个线程拿到锁去实例化操作,而其他线程需要等待,而实例化好的线程释放所后,后进入的线程如果不判断单例是否已经产生,那么就会又去实例化一个对象,如此就不能实现单例了。

如此双重检验锁开启来完美,而指令重排序会引起问题。我想这也是一个学习重排序的好例子。

instance = new Singleton();

上面这个代码不是一个原子操作,即无法被翻译成一个指令完成。

它是由一下3个指令完成的:

给 instance 分配内存
调用 Singleton 的构造函数来初始化成员变量
将instance对象指向分配的内存空间地址

JVM编译时进行指令重排序可能打乱上面三个指令的执行顺序,也就是说可能先直行来1,3然后执行2。那么有这么一种情况当执行好1和3,instance不为null,新进入的线程在判断第一个null时就会直接返回一个没有执行2步骤的实例,如此就有不符合期望了。这的确是个经典的场景。

额外阅读

如果我们在实例初始化后,将第三步,分开写,似乎可以解决这个问题,代码如下:

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

volatile关键字事实上是对编译时的重排序进行了屏障。具体各家说法可以阅读下面的文章:

扩展阅读

由上可以感受到,在累加载时就初始化好实例,会有很多需要考虑的东西,那么如果在编译阶段就实例化好,如此就可以避免并发带来的问题。

那就是所谓的饿汉式单例:

public class Singleton{

    //类加载时就初始化
    private static final Singleton instance = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return instance;
    }
}

当然这样做自然有自己的弊端,就是这个单例在没有被使用到的时候就已经需要实例化出来,如此就会占用无谓占用内存,如果这个实例初始化复杂占用资源,而实际未必会使用就比较尴尬了。

或者说,这种方式实例化将无法实现依赖外部参数实例化的场景。

还有一种推荐写法:

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

还有一种大师推荐的写法,有没有很高大上:

public enum EasySingleton{

    INSTANCE;
}

我们来看看如何破坏单例:

1,序列化与反序列化

当然我们前面写的代码不需要序列化和反序列化,就没有这个问题了,只是说送这个方面我们考虑如何破坏它,参考如下例子:

public class Singleton implements Serializable{
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
public class SerializableDemo1 {
    //为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
    //Exception直接抛出
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Write Obj to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        oos.writeObject(Singleton.getSingleton());
        //Read Obj from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        Singleton newInstance = (Singleton) ois.readObject();
        //判断是否是同一个对象
        System.out.println(newInstance == Singleton.getSingleton());
    }
}
//false

2,反射

public class Singleton {
    public static final Singleton INSTANCE = new Singleton();
    private Singleton() {
    }
    public Singleton getInstance() {
        return INSTANCE;
    }
    public static void main(String[] args) throws Exception {
        // 反射机制破坏单例模式
        Class clazz = Singleton.class;
        Constructor c = clazz.getDeclaredConstructor();
        // 反射机制使得private方法可以被访问!!!
        c.setAccessible(true);
        // 判断反射生成的对象与单例对象是否相等
        System.out.println(Singleton.INSTANCE == c.newInstance());
    }
}

破坏单例的原理就是,不走构造函数即可产生实例的方式,因为我们只关闭了构造函数。

至此,对java单例有一个比较全面的认识,牵涉到大量知识点,需要继续挖掘。

让我们继续前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不会成功。

时间: 2024-12-15 01:53:48

java单例-积木系列的相关文章

java单例类/

java单例类  一个类只能创建一个实例,那么这个类就是一个单例类 可以重写toString方法 输出想要输出的内容 可以重写equcal来比较想要比较的内容是否相等 对于final修饰的成员变量 一但有了初始值,就不能被重新赋值 static修饰的成员变量可以在静态代码块中 或申明该成员时指定初始值 实例成员可以在非静态代码块中,申明属性,或构造器中指定初始值 final修饰的变量必须要显示初始化 final修饰引用变量不能被重新赋值但是可以改变引用对象的内容分(只要地址值不变) final修

Java 单例

最近在网上看到一篇关于 Java 单例的创建问题,虽然是一个 Java 程序员,但是到现在还没有真正的深入了解到 Java 的原理和机制.所以每每看到这样能够"真正"接触 Java 的机会内心总是充满了欣喜.记录下,以后备用. 懒汉模式 public class Singlton{ private static Singleton instance; private Singlton(){} public static Singlton getInstance(){ if(instac

Java单例的实现

1.声明实例变量(静态) 2.私有化构造函数 3.创建获取实例的方法 public class Singleton{ //创建实例变量 private static Singleton singleton; //私有化构造函数 private Singleton(){ } //创建获取实例的方法 public static Singleton getInstance(){ if(singleton==null){ singleton=new Singleton(); } return singl

Java单例设计模式的实现

1. 单例设计模式的定义 单例设计模式确保类只有一个实例对象,类本身负责创建自己的对象并向整个系统提供这个实例.在访问这个对象的时候,访问者可以直接获取到这个唯一对象而不必由访问者进行实例化. 单例设计模式保证了全局对象的唯一性,在许多场景中都有应用.例如Windows中多个进程或线程同时操作一个文件的时候,所有的进程或线程对于同一个文件的处理必须通过唯一的实例来进行. 2. java单例设计模式的几种实现方式 单例设计的最大特点是类的构造函数是私有的,确保了只能由类本身创建对象,而访问者无法进

Java 单例真的写对了么?

单例模式是最简单的设计模式,实现也非常“简单”.一直以为我写没有问题,直到被 Coverity 打脸. 1. 暴露问题 前段时间,有段代码被 Coverity 警告了,简化一下代码如下,为了方便后面分析,我在这里标上了一些序号: private static SettingsDbHelper sInst = null; public static SettingsDbHelper getInstance(Context context) { if (sInst == null) { // 1 s

Java——单例设计模式

设计模式:解决某一类问题最行之有效的方法.Java中23种设计模式:单例设计模式:解决一个类在内存中只存在一个对象. 想要保证对象唯一.1,为了避免其他程序过多建立该类对象.先禁止其他程序建立该类对象2,还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象.3,为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式 这三部怎么用代码体现呢?1,将构造函数私有化.2,在类中创建一个本类对象.3,提供一个方法可以获取到该类对象. 对于事物该怎么描述,还怎么描述.当需要将该事物的对象

Java 单例总结

1:懒汉式,不保证线程安全 package com.yan.singleton; public class LazySingleton { private static final LazySingleton instance = null; private LazySingleton(){} public static LazySingleton getLazySingleton(){ if(null==instance){ return new LazySingleton(); } retu

Java: 单例设计模式

设计模式: * 设计模式:解决某一类问题最行之有效的方法:* Java有23中设计模式* 单例设计模式:解决一个类在内存只存在一个对象:* * 想要保证对象唯一* 1.为了避免其他程序过多建立该类对象.先控制禁止其他程序建立该类对象* 2.还为了让其他程序可以访问到该类对象,只好在本类中自定义一个对象* 3.为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式* * 这三部如何用代码体现呢?* 1. 将构造函数私有化* 2. 在类中建立一个本类对象* 3. 提供一个方法可以获取到该对象

从一个简单的Java单例示例谈谈并发

一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static UnsafeLazyInitiallization instance; private UnsafeLazyInitiallization() { } public static UnsafeLazyInitiallization getInstance(){ if(instance==null){ /