单例模式是一种创建型模式,它确保程序中一个类最多只有一个实例。
实现单例模式需要注意的几个点:
①需要提供一个静态的获取该类的方法;
②构造方法必须是私有的;
那么都有哪些常见的创建单例的方法呢,这些线程是否安全呢?
第一种:饿汉式
饿汉式单例是在程序加载的时候就创建了这个对象的实例:
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类的加载和初始化过程都是线程安全的。