java单例模式

1.饿汉式

package demo5;

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

提供了一个静态实例并返回给调用者

饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。优点是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。缺点是即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。

2.懒汉式

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

懒汉模式是在需要的时候才会被创建,如果已经创建了,那再次调用的时候就不会创建新的对象,而是返回之前创建的对象。但是懒汉模式并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁解决线程同步问题。如下

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

 3.双重校验锁

上面加锁的懒汉式存在着性能问题,如果多次调用newInstance(),累计下来的性能损耗就比较大。因此就有了如下的双重校验锁。

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

再次调用getInstance()只需要直接返回单例对象。所以大部分情况下,调用getInstance()都不会执行到同步代码块。不过还需要考虑一种情况,假如两个线程,一个线程执行了if (instance == null)语句,就会认为单例对象没有创建,此时另一个线程也执行了同样的语句,认为单例对象没有创建,然后两个线程依次执行同步代码块,各自创建了一个单例对象。所以需要在同步代码块中增加if (instance == null)语句。由于JVM底层模型原因,偶尔会出问题,不过JDK1.5及之后版本增加了volatile关键字。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的。

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

4.静态内部类

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

外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化,这样就不会占用内存。只有当newInstance第一次被调用时,才会初始化INSTANCE,第一次调用会加载SingletonHolder,这样不仅是线程安全的,也能保证单例的唯一性并且延迟了单例的实例化。

5.枚举类

public enum Singleton{
    instance;
    public void whateverMethod(){}    
}

枚举与普通类一样,可以有字段也可以有方法,并且枚举的实例创建是线程安全的,并且在任何情况都是单例的,但不能延时加载,虽然优点很多,但在主流开发中却很少用到。

 懒汉式和饿汉式在使用过程中都有一定的缺陷,在使用过程中一般不建议使用,双重校验锁和静态内部类很好的规避了前面两个的缺点,也是推荐的两种使用方式。

原文地址:https://www.cnblogs.com/xiayq/p/10508254.html