单例模式(Singleton Pattern)是一种非常简单的设计模式之一,当我们使用的对象要在全局唯一时就需要用到该模式,以保证对象的唯一性。除此之外,还能避免反复的实例化对象,减少内存开销
单例类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
单例主要有如下创建方式
/*** 1、饿汉式-静态变量(线程安全)*/
public class Hungry1 {// 可能会浪费空间private byte[] data1 = new byte[1024*1024];private byte[] data2 = new byte[1024*1024];// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Hungry1(){}// 2、内部创建对象private static Hungry1 singleton = new Hungry1();// 3、对外获取实例的方法public static Hungry1 getInstance(){return singleton;}
}
优点:写法简单;避免了线程同步问题
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
/*** 2、饿汉式-静态代码块(线程安全)*/
public class Hungry2 {// 可能会浪费空间private byte[] data1 = new byte[1024*1024];private byte[] data2 = new byte[1024*1024];// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Hungry2(){}// 2、内部静态代码块中创建对象private static Hungry2 singleton;static{singleton = new Hungry2();}// 3、对外获取实例的方法public static Hungry2 getInstance(){return singleton;}
}
优缺点同上
/*** 3、懒汉式-简单判断非空(多线程并发不安全,单线程无影响)*/
public class Lazy1 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy1() {}// 2、声明类成员变量singletonprivate static Lazy1 singleton;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static Lazy1 getInstance(){if (singleton == null){singleton = new Lazy1();}return singleton;}
}
优点:在使用时才会生成对象,能够减少内存开销
缺点:线程不安全,只适用单线程,当有多个线程访问时,能够产生多个对象,不满足单例模式的要求
在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
/*** 4、懒汉式-synchronized锁方法(线程安全,效率低)*/
public class Lazy2 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy2() {}// 2、声明类成员变量singletonprivate static Lazy2 singleton;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static synchronized Lazy2 getInstance(){if (singleton == null){singleton = new Lazy2();}return singleton;}
}
优点:线程安全
缺点:效率太低,synchronized锁了整个方法,下一个线程必须等上一个线程释放锁,效率很低,不建议使用
/*** 5、懒汉式-synchronized同步代码块(多线程并发不安全)*/
public class Lazy3 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy3() {}// 2、声明类成员变量singletonprivate static Lazy3 singleton;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static synchronized Lazy3 getInstance(){if (singleton == null){synchronized (Lazy3.class){singleton = new Lazy3();}}return singleton;}
}
优点:在使用时才会生成对象,能够减少内存开销
缺点:线程不安全
假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
/*** 6、懒汉式-双重检查(线程安全;常用)*/
public class Lazy4 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy4() {}// 2、声明类成员变量singletonprivate volatile static Lazy4 singleton;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static Lazy4 getInstance(){if (singleton == null){synchronized (Lazy4.class){if (singleton == null){/* 有可能 非原子性操作* 1、分配内存空间* 2、执行构造方法,初始化对象* 3、把这个对象指向这个空间** 123* 132 ;此时lazyMan还没有完成构造,报出空指针异常,最好加上volatile修饰*/singleton = new Lazy4();}}}return singleton;}
}
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象
优点:效率高,线程安全
缺点:可通过反射破坏单例模式
/*** 6、懒汉式-双重检查(线程安全;常用)** 1、反射破坏双重懒汉式单例--破坏私有无参构造器*/
public class Lazy4Reflect {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy4Reflect() {}// 2、声明类成员变量singletonprivate static Lazy4Reflect singleton;//private static boolean isReflect = false;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static Lazy4Reflect getInstance(){if (singleton == null){synchronized (Lazy4Reflect.class){if (singleton == null){/* 有可能 非原子性操作* 1、分配内存空间* 2、执行构造方法,初始化对象* 3、把这个对象指向这个空间** 123* 132 ;此时lazyMan还没有完成构造,报出空指针异常*/singleton = new Lazy4Reflect();}}}return singleton;}
}class Test{public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 反射破坏单例Lazy4Reflect instance = Lazy4Reflect.getInstance();Constructor constructor = Lazy4Reflect.class.getDeclaredConstructor(null);// 忽略私有构造器constructor.setAccessible(true);Lazy4Reflect instance1 = constructor.newInstance();System.out.println(instance);System.out.println(instance1);}
}
输出如下,两个 instance内存地址明显不同
/*** 6、懒汉式-双重检查(线程安全;常用)** 2、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁也破坏*/
public class Lazy4Reflect1 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy4Reflect1() {synchronized (Lazy4Reflect1.class){if (singleton != null){throw new RuntimeException("不要使用反射破坏异常");}}}// 2、声明类成员变量singletonprivate static Lazy4Reflect1 singleton;//private static boolean isReflect = false;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static Lazy4Reflect1 getInstance(){if (singleton == null){synchronized (Lazy4Reflect1.class){if (singleton == null){/* 有可能 非原子性操作* 1、分配内存空间* 2、执行构造方法,初始化对象* 3、把这个对象指向这个空间** 123* 132 ;此时lazyMan还没有完成构造,报出空指针异常*/singleton = new Lazy4Reflect1();}}}return singleton;}
}class Test1 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 反射破坏单例//Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();Constructor constructor = Lazy4Reflect1.class.getDeclaredConstructor(null);// 忽略私有构造器constructor.setAccessible(true);// 两个对象都不经过 getInstance()创建,反射拿到Lazy4Reflect1 instance = constructor.newInstance();Lazy4Reflect1 instance1 = constructor.newInstance();System.out.println(instance);System.out.println(instance1);}
}
/*** 6、懒汉式-双重检查(线程安全;常用)** 3、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁,加标志位也破坏*/
public class Lazy4Reflect2 {// 1、将构造方法私有化,外部无法使用new构造方法创建实例private Lazy4Reflect2() {synchronized (Lazy4Reflect2.class){if (isReflect = false){isReflect = true;}else {throw new RuntimeException("不要使用反射破坏异常");}}}// 2、声明类成员变量singletonprivate static Lazy4Reflect2 singleton;private static boolean isReflect = false;// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建public static Lazy4Reflect2 getInstance(){if (singleton == null){synchronized (Lazy4Reflect2.class){if (singleton == null){/* 有可能 非原子性操作* 1、分配内存空间* 2、执行构造方法,初始化对象* 3、把这个对象指向这个空间** 123* 132 ;此时lazyMan还没有完成构造,报出空指针异常*/singleton = new Lazy4Reflect2();}}}return singleton;}
}class Test2 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {// 反射破坏单例Field isReflectField = Lazy4Reflect2.class.getDeclaredField("isReflect");isReflectField.setAccessible(true);//Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();Constructor constructor = Lazy4Reflect2.class.getDeclaredConstructor(null);// 忽略私有构造器constructor.setAccessible(true);// 两个对象都不经过 getInstance()创建,反射拿到Lazy4Reflect2 instance = constructor.newInstance();isReflectField.set(instance,false);Lazy4Reflect2 instance1 = constructor.newInstance();System.out.println(instance);System.out.println(instance1);}
}
/*** 7、静态内部类(线程安全;常用)*/
public class StaticInnerClass {// 1、将构造方法私有化,外部无法使用newprivate StaticInnerClass(){}// 2、一个静态内部类 创建实例private static class StaticInstance{private static final StaticInnerClass INSTENCE = new StaticInnerClass();}// 3、直接调用静态内部类,返回instancepublic static StaticInnerClass getInstance(){return StaticInstance.INSTENCE;}
}
这种方式跟饿汉式方式采用的机制类似,但又有不同。
两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
优点:线程安全,延迟加载,效率高
缺点: 可通过反射破坏单例模式
/*** 7、静态内部类(线程安全;常用)* * 可以通过反射破坏*/
public class StaticInnerClass {// 1、将构造方法私有化,外部无法使用newprivate StaticInnerClass(){}// 2、一个静态内部类 创建实例private static class StaticInstance{private static final StaticInnerClass INSTENCE = new StaticInnerClass();}// 3、直接调用静态内部类,返回instancepublic static StaticInnerClass getInstance(){return StaticInstance.INSTENCE;}
}class TestStaticInner{public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 反射破坏单例StaticInnerClass instance = StaticInnerClass.getInstance();Constructor constructor = StaticInnerClass.class.getDeclaredConstructor(null);// 忽略私有构造器constructor.setAccessible(true);StaticInnerClass instance1 = constructor.newInstance();System.out.println(instance);System.out.println(instance1);}
}
/*** 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)*/
public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;}
}
优点:线程安全,不用担心反射破坏单例模式
缺点:枚举类占用内存多
public final class Constructor extends Executable {@CallerSensitivepublic T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class> caller = Reflection.getCallerClass();checkAccess(caller, clazz, null, modifiers);}}if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");ConstructorAccessor ca = constructorAccessor; // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;}
}
尝试使用反射破坏
/*** 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)*/
public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;}
}class TestEnumSingleton{public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 反射破坏单例EnumSingleton instance = EnumSingleton.INSTANCE;// java.lang.NoSuchMethodException//Constructor constructor = EnumSingleton.class.getDeclaredConstructor(null);// 注:通过 jad 反编译得到 有参构造Constructor constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);// 忽略私有构造器constructor.setAccessible(true);// java.lang.NoSuchMethodExceptionEnumSingleton instance1 = constructor.newInstance();System.out.println(instance);System.out.println(instance1);}
}
通过 jad 反编译得到 有参构造
直接报错
单例模式分为懒汉式、饿汉式、饿汉式同步锁、双重校验锁、静态内部类、枚举类,每一种都有自己的优缺点,可以根据自己的项目实际需要选择适合的单例模式
上一篇:关于乔布斯的18个细节
下一篇:大学生找工作的新敲门砖