单例模式是一种创建型模式,它确保程序中一个类最多只有一个实例。

实现单例模式需要注意的几个点:

①需要提供一个静态的获取该类的方法;

②构造方法必须是私有的;

那么都有哪些常见的创建单例的方法呢,这些线程是否安全呢?

第一种:饿汉式 

饿汉式单例是在程序加载的时候就创建了这个对象的实例:

public class HungrySingleton {

    private  static  HungrySingleton instancc = new HungrySingleton();
    private HungrySingleton(){};

    public static HungrySingleton getInstance(){
        return  instancc;
    }

}

在静态初始化的时候创建单例,JVM在加载这个类的时候就创建此唯一的单实例,JVM保证在任何线程访问instance静态变量之前,一定先创建实例。

但是,当创建一个单例需要消耗大量资源时,而且希望只在程序使用的时候才初始化,那么就需要延迟加载了。

第二种:懒汉式

public class LazySingleton {

    private LazySingleton(){};

    private static LazySingleton instance = null;

    public LazySingleton getInstance(){
        //在被调用的时候才创建
        if (null == instance){
            instance = new LazySingleton();
        }
        return instance;
    }
}

但是,这种创建单例的方法在多线程访问的情况下是不安全的,会产生多个实例,下面用代码演示一下多线程访问下的安全性问题:

public class SingletonTest {

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread() {
                @Override
                public void run() {
                LazySingleton singleton = LazySingleton.getInstance();
                System.out.println("当前时间"+System.currentTimeMillis()+"------->"+singleton);
                }
            }.start();

        }
    }

}

模拟了一下多线程访问这个方法的时候,发现这个实例并不是唯一的,在同一时间出现了两个实例:
当前时间1554299067525------->chenhuan.designpattern.singleton.LazySingleton@327d19c0
当前时间1554299067525------->chenhuan.designpattern.singleton.LazySingleton@268d1cb9
当前时间1554299067525------->chenhuan.designpattern.singleton.LazySingleton@268d1cb9

所以,我们需要对这个创建方式加锁:

public class LazySingleton {

    private LazySingleton(){};

    private static LazySingleton instance = null;

    public static synchronized LazySingleton getInstance(){
        //在被调用的时候才创建
        if (null == instance){
            instance = new LazySingleton();
        }
        return instance;
    }
}

加了锁之后,性能会下降很多,我们发现,其实只有在第一次进入的时候才需要加锁,当instance变量被赋值之后,就不需要这个同步了。我们可以利用双重检查加锁来提高它的性能。
public final class SychronizedLazySingleton {

    private SychronizedLazySingleton(){ };
    /**
     * 声明单例变量
     */
    private volatile static SychronizedLazySingleton instance = null;

    public static SychronizedLazySingleton getInstance(){

        //这边加一个判断是为了不让每一步都去拿这个锁,加快效率
        if (null == instance){
            synchronized (SychronizedLazySingleton.class){
                if (null == instance){
                    instance = new SychronizedLazySingleton();
                }
            }
        }
        return instance;
    }

}

 volatile关键字是为了防止指令的重排序,在多线程下保证变量的可见性。

第三种:静态内部类的方式

public class HolderSingleton {

    private  HolderSingleton(){};

    public static class Singleton{
        private static HolderSingleton instance = new HolderSingleton();

    }

    //调用 Singleton.instance 的时候,才会对单例进行初始化。 
    public static HolderSingleton getInstance(){
        return  Singleton.instance;
    }
}

由于在调用 SingletonHolder.instance 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的。所以这种形式,很好的避免了反射入侵。

第四种:枚举

public enum SingletonEnum {
    INSTANCE;
    public void getInstance(){
    };
}

利用javap我们反编译枚举类,发现枚举类会被转换成形如public final class T extends Enum的定义,而且,枚举中的各个枚举项项是通过static来定义的,

当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。

04-07 03:22