什么是单例模式?
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的优点?
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用。

关键:构造器是私有的

注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

常见的几种单例模式:

单例模式有很多种写法,大部分写法都或多或少有一些不足。下面将分别对这几种写法进行介绍。
## 饿汉式 ##

public class SingleObject {
    //创建 SingleObject 的一个对象
    private static SingleObject instance = new SingleObject();
    //让构造函数为 private,这样该类就不会被实例化
    private SingleObject(){}
    //获取唯一可用的对象
    public static SingleObject getInstance(){
        return instance;
    }
}

从上面代码中可以看出,类的构造器用private修饰,保证其他类不能够实例化该类,然后该类自己创建了一个静态实例,并用getInstance()方法返回该实例。

饿汉式单例模式在类加载的时候就创建了该类的实例对象,实例对象在程序的整个运行周期中都是存在的,不会存在多个线程创建多个实例的情况,避免了多线程的同步问题。

但是,该类的缺点也很明显,由于没有延迟加载,就是在该类的实例还没使用之前就已经在加载过程创建了实例,即使该实例不会被用到也会被创建出来,会浪费内存空间。

## 懒汉式—线程不安全 ##

public class SingleObject2 {
    //设置一个该类对象的静态成员变量
    private static SingleObject2 instance;  
    //让构造函数为 private,这样该类就不会被实例化
    private SingleObject2(){}   
    //获取唯一可用的对象
    public static SingleObject2 getInstance(){  
        //判断实例对象是否为空,如果为空就创建实例对象
        if (instance == null) {  
            instance = new SingleObject2();  
      }  
      return instance; 
    }
}

由上述代码可知,同样是用private修饰的构造器,使其不被其他类实例化,但是该类将自己的实例对象作为一个静态的私有的属性,并且没有new实例化,在getInstance()方法中判断instance实例对象是否为空,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。

如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这时候使用这个方法就可以。但是,上面代码并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁synchronize解决线程同步问题。

## 懒汉式—线程安全 ##

public class SingleObject3 {

    //设置一个该类对象的静态成员变量
    private static SingleObject3 instance;  
    //让构造函数为 private,这样该类就不会被实例化
    private SingleObject3(){}   
    //获取唯一可用的对象  使用了synchronize锁旗标,线程安全
    public static synchronized SingleObject3 getInstance(){ 
        //判断实例对象是否为空,如果为空就创建实例对象
        if(instance == null){     
            return instance = new SingleObject3();     
        }else{     
            return instance;     
        }     
    }
}

以上代码只是在懒汉式的基础上在getInstance()方法上用了synchronize锁旗标,以保证线程安全,但是,用synchronize修饰的方法的运行速度会远远的慢于普通方法,如果多次调用,就会大大的降低程序的运行速度。

## 双重校验/双检锁 ##

public class SingleObject4 {
    //设置一个该类对象的静态成员变量
    private static SingleObject4 instance;  
    //让构造函数为 private,这样该类就不会被实例化
    private SingleObject4(){}   
    //获取唯一可用的对象
    public static  SingleObject4 getInstance(){ 

        //判断实例对象是否为空,双重判断
        if(instance == null){     
            synchronized(SingleObject4.class){
                if (instance == null ) {
                    instance = new SingleObject4();
                }
            }
        }
        return instance;
    }
}

可以看到上面在同步代码块外多了一层instance为空的判断。由于单例对象只需要创建一次,如果后面再次调用getInstance()只需要直接返回单例对象。因此,大部分情况下,调getInstance()都不会执行到同步代码块,从而提高了程序性能。不过还需要考虑一种情况,假如两个线程A、B,A执行了if (instance == null)语句,它会认为单例对象没有创建,此时线程切到B也执行了同样的语句,B也认为单例对象没有创建,然后两个线程依次执行同步代码块,并分别创建了一个单例对象。为了解决这个问题,还需要在同步代码块中增加if (instance == null)语句,也就是上面看到的第二层判定的代码。

  • 双重校验锁即实现了延迟加载,又解决了线程并发问题,同时还解决了执行效率问题。

但是,在这里不得不提起,Java指令中的重排优化,它是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。

由于指令重排优化的存在,导致初始化SingletonObject4和将对象地址赋给instance字段的顺序是不确定的。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance(),取到的就是状态不正确的对象,程序就会出错。

不过在JDK1.5及之后版本增加了volatile关键字。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。

下面就是增加了volatile关键字之后的代码:

public class SingleObject4 {
    //设置一个该类对象的静态成员变量
    private static volatile  SingleObject4 instance;    
    //让构造函数为 private,这样该类就不会被实例化
    private SingleObject4(){}   
    //获取唯一可用的对象
    public static  SingleObject4 getInstance(){ 

        //判断实例对象是否为空,双重判断
        if(instance == null){     
            synchronized(SingleObject4.class){
                if (instance == null ) {
                    instance = new SingleObject4();
                }
            }
        }
        return instance;
    }
}

## 登记式/静态内部类 ##

public class SingleObject5 {
    //利用静态内部类获取当前类的实例变量
    private static class SingletonHolder {  
        private static final SingleObject5 INSTANCE = new SingleObject5();  
    }
    //私有构造器
    private SingleObject5 (){}  
    //获取唯一可用对象
    public static final SingleObject5 getInstance() {  
        return SingletonHolder.INSTANCE;  
    } 
}

该方法同样利用了类加载机制来保证只创建一个instance实例。它与饿汉式一样,也是利用了类加载机制,由于实例化对象实在静态内部类中进行的,所以只要不调用静态内部类就不会 实例化该对象,因此不存在多线程并发的问题。

## 枚举 ##

public enum SingleObject6 {
     INSTANCE;  
    public void whateverMethod(){}
}

看着很简单,But,我还没看懂~
请原谅我这个小白~~
看到一个大神的博客上是这样写的:

上面的几种实现单例的方式都有共同的缺点:
1)需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》作者推荐使用的方法。不过,在实际工作中,很少看见有人这么写。

大神总结

双重校验锁和静态内部类的方式可以解决大部分问题,平时工作中使用的最多的也是这两种方式。枚举方式虽然很完美的解决了各种问题,但是这种写法多少让人感觉有些生疏。