单例模式(Singleton Pattern)


单例模式(Singleton Pattern)

定义

确保一个类只有一个实例,并提供一个全局访问点。

类图

懒汉式

所谓的懒汉式就是延迟对象实例化

代码

public class Singleton {
    private static Singleton singleton;

    private Singleton(){
        System.out.println("我是单例模式独一无二的对象");
    };

    //对象只有在调用了该方法后才进行实例化
    public static Singleton getInstance() {
        if(singleton==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

单线程测试

public class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton =Singleton.getInstance();
        singleton = Singleton.getInstance();
        singleton = Singleton.getInstance();
        singleton = Singleton.getInstance();
    }
}

多线程测试

public class SingletonTest {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                Singleton singleton =Singleton.getInstance();
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                Singleton singleton =Singleton.getInstance();
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                Singleton singleton =Singleton.getInstance();
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                Singleton singleton =Singleton.getInstance();
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                Singleton singleton =Singleton.getInstance();
            }
        }.start();
    }
}

测试结果:

出现结果分析

简单改进

//修改getInstance()方法,给它加synchronized 关键字
 public synchronized static Singleton getInstance() {
        if(singleton==null){
            singleton = new Singleton();
        }
        return singleton;
    }

但这样子真的好嘛?仔细想想,好像确实对性能的影响很大,因为我们只在第一次执行这个方法时,才真正需要同步,换句话说,一旦设置好singleton变量,就不再需要同步这个方法了,之后调用这个方法,同步都是一种累赘。

改善多线程的方法

  1. 如果getInstance()的性能对应用程序不是很关键,那就不需要改了!
    同步getInstance()的方法既简单又有效。但你必须知道,同步一个方法可能造成程序执行效率下降100倍。应此,如果将getInstance()的程序使用在频繁运行的地方,你可能就得重新考虑了!
  2. 使用“急切”(饿汉式)创建实例,而不用延迟实例化(懒汉式)的做法。
    如果程序总是创建并使用单例实例,或者在创建和运行时方面的负担不太繁重,可以使用此方法
public class Singleton {
    private static Singleton singleton =new Singleton();
    //在静态初始化器中创建单例。这段代码保证了线程安全。

    private Singleton(){
        System.out.println("我是单例模式独一无二的对象");
    };

    public static Singleton getInstance() {
        return singleton;//已经有实例了,直接使用它
    }
}
  1. 用“双重检索加锁”,在getInstance()中减少使用同步
    如果性能是你关注的重点,那么这个做法可以帮你大大减少getInstance()的时间耗费。
public class Singleton {
    private volatile static Singleton singleton;

    private Singleton(){
        System.out.println("我是单例模式独一无二的对象");
    };

    public static Singleton getInstance() {  
        if(singleton==null){//检查是否已经创建
            synchronized(singleton){//未创建,则进入同步
                if(singleton==null){//再检查一次,如果仍是null,才创建实例
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

补充:
volatile关键词确保singleton变量被初始化成Singleton实例时,多线程正确地处理singleton变量

总结

懒汉式:延迟了实例化,在单线程模式下,能达到单例模式的效果,但在多线程环境时,就会出现意想不到的bug,简单的在getInstance()方法上加synchronized,会降低多线程的执行效率,可以利用“双重检索加锁”的方法,提高效率
饿汉式:在静态初始化器中创建单例,如果程序总是创建并使用单例实例,或者在创建和运行时方面的负担不太繁重,可以使用此方法,它将不再使用延迟实例化的方法创建单例,依赖JVM在加载这个类时,马上创建此唯一单例,保证在任何线程访问是singleton变量时,一定先创建此实例!