java 设计模式-单例模式

目录

单例模式: (单例:一个类单个实例)  

1.饿汉式:

2.懒汉式

懒汉式优化:双重检查机制

懒汉双重检查机制为什么要加volatile:

枚举饿汉式:

内部懒汉式:


设计模式是对已有问题固定的解决方法的总结。

单例模式: (单例:一个类单个实例)  

单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问他的全局访问点。

保证类仅有一个实例最好的办法就是,让类自身负责保存他的唯一实例。这个类可以保证没有其他实例被创建,并且他可以提供一个访问该实例的方法。

单例模式保证只有一个实例,就要保证外不能随便的new这个对象,所以要 私有化构造方法
私有化构造方法后就是 new这个对象控制权收回了,只能在类内部去实例化这个对象, 让类自身负责保存他的唯一实例。 
应用场景举例:
        

1. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~ 

2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。

4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

两种方式:
       1. 饿汉式
        2.懒汉式

1.饿汉式:

从时间空间上看:以空间换时间 

从线程安全上看:安全的

所以又叫“饿汉式”(还没有使用就实例化了)

public class Singleton {
   //3、定义一个变量来存储创建好的类实例
   //直接在这里创建类实例,会放到静态代码块中执行,静态代码块里面的线程安全由虚拟机来保证
   //这个类加载到内存的时候就会创建唯一的一份实例
   private static final Singleton INSTANCE = new Singleton();
   
   //1.私有化构造方法,好在内部控制创建实例的数目
   private Singleton() {
      
   }
   
   //2、定义一个方法来为客户端提供类实例
    //这个方法需要定义成类方法,也就是要加static
   public static Singleton getInstance() {
      //4、直接使用已经创建好的实例
      return INSTANCE ;
   }
}

2.懒汉式

从时间空间上看:以时间换空间

从线程安全上看:不安全的(所以要加锁synchronized)

所以又叫“懒汉式”(也就是在用到的时候才去实例化)

public class Singleton1 {
   //3、定义一个变量来存储创建好的类实例,先不new,用到时候再new
   //因为这个变量要在静态方法中使用,所以需要加上static修饰
   private static Singleton1 INSTANCE = null;
   
   //1.私有化构造方法,好在内部控制创建实例的数目
   private Singleton1() {
      
   }
   
   //2、定义一个方法来为客户端提供类实例
   //这个方法需要定义成类方法,也就是要加static
   public synchronized static Singleton1 getInstance() {
      //4、判断这个实例是不是有值
      // 这段代码有线程安全问题,两个线程同时判断为null可能会new多个,加锁解决  
      if (INSTANCE == null) {
         //5、如果没有,就创建一个类实例,并把值
         //赋给存储类实例的变量
        INSTANCE = new Singleton1();
      }
      returnINSTANCE ;
   }
}

懒汉式加锁,每次访问都要加锁,但其实只有INSTANCE为null,也就是第一次创建前才有线程安全问题,这样每次加锁耗费大量的资源所以我们对其进行优化。

懒汉式优化:双重检查机制


public class Singleton3 {
    // volatile解决共享变量可见性,有序性(禁止指令重排),但是解决不了原子性
    private static volatile Singleton3 INSTANCE;

    private Singleton3(){}

    
    public synchronized static Singleton3 getINSTANCE() {
        // 双重检查
        if(INSTANCE == null) {
            synchronized (Singleton3.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton3();
                }
            }

        }

        return INSTANCE;
    }
}

如果实例存在就没有必要走同步的必要,如果实例不存在才会进入同步块。

这样只会在第一次创建的时候同步一次,其余的时候不需要同步。

第二重检查是因为:当A线程执行 new Singleton()时候,B线程正在执行第一重检查if(instance == null)此时B线程会进入,所以在synchronized中要再做一次检查。

因为instance是volatile修饰的所以A线程new Singleton修改了instance的时候,在B线程中能读取到instance不是null。


懒汉双重检查机制为什么要加volatile:

volatile

解决共享变量可见性,有序性(禁止指令重排),但是解决不了原子性

可见性:

在一个线程修改这个变量时,其他的线程也能察觉。正常情况是不能察觉的,因为线程操作的是变量副本。

原子性:

简单来说,原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。

比如:m = 6; // 这是个原子操作

        int  n=6;//这不是一个原子操作,对于这个语句,至少有两个操作:

                ①声明一个变量n ②给n赋值为6。       

指令重排: 

CPU乱序执行,乱序执行是相对于顺序执行来说的,CPU可能会对指令的执行次序做出优化,如果指令之间没有因果关系,CPU可能调换他们的执行次序。

简单来说,就是指你在程序中写的代码,在执行时并不一定按照写的顺序。

这里用volatile关键字,除了利用他的可见性之外更重要的是利用它禁止指令重排的特性。

我们将上边的代码反编译得到:

先来解读一下:

17条:new出了对象,分配空间,但是没有代用构造方法。

21条:指构造方法,给上边new出的空间放入了东西。

24条:将17条new出的空间地址赋给INSTANCE变量。

可以看出,17条与21条,17条与24条是有逻辑关系的,顺序不能打乱,但是21条与24条没有逻辑关系,顺序可以打乱,也就是没有volatile时可以重排。

如果我们将其重排,在懒汉式双重检查机制多线程下就会出问题:

 java 设计模式-单例模式_第1张图片

 如图,如果线程1在执行代码 INSRANCE = new Singleton4(); 时,按照17条,24条,21条的顺序执行,在执行完24条,也就是把new出来的对象(有空间但是没经过构造方法)赋给了INSTANCE,这时INSTANCE不为空,此时线程2来进行第一重判断,会直接将这个INSTANCE变量返回。这是我们不允许的。

而volatile通过内存屏障可以禁止指令重排序,按照new、invokespecial、putstatic顺序执行。

就解决了这一问题。


枚举饿汉式:

就是利用枚举类实现

枚举天然能防止反射、反序列化破坏单例

public enum Singleton4 {
    // Singleton4 INSTANCE;
    INSTANCE;

    // 枚举构造方法默认就是私有的,去掉也可以
    private Singleton4() {
        System.out.println("private Singleton4()");
    }

    public static Singleton4 getInstance() {
        return INSTANCE;
    }

}

内部类懒汉式:

上边的例子中,饿汉式需要提前new出对象,占用空间;懒汉式有线程安全问题,加锁开销大。

有没有一种方法既能不提前new出单例又能不加锁呢,有,那就是内部懒汉式。

利用内部类实现:

// 懒汉式单例 - 内部类
public class Singleton5 {
    private Singleton5() {
        System.out.println("private Singleton5()");
    }

    //没有用到这个静态内部类不会触发他的加载、连接、初始化,也就不会初始化对象
    private static class Holder {
        // 会放到静态代码块中执行,静态代码块里面的线程安全由虚拟机来保证
        static Singleton5 INSTANCE = new Singleton5();
    }

    // 懒汉式,不调用getInstance时候不会触发Holder的加载和初始化
    // 既保证了线程安全static静态保证,避免了双检锁的缺点,又保证懒汉特性,第一次访问才用到
    public static Singleton5 getInstance() {
        return Holder.INSTANCE;
    }
}

你可能感兴趣的:(单例模式,java,设计模式)