特点:
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这个实例。
优点:
提供了对唯一实例的受控访问;
由于系统内存中只存在一个对象,因此可节约系统的资源,对于一些频繁的创建和销毁的对象,单例模式可以提升系统的性能。
单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例在内存中,避免了对同一个资源文件的重复写操作。
单例模式可以在系统设置全局的访问点,优化和共享访问,例如:设计一个单例类,负责所有数据表的映射处理。
缺点:
单例模式一般没有接口,扩展很困难,如果要扩展,则必须修改代码。
单例类职责过重,在一定的程度上违背了单一职责。
滥用单例模式会带来一些负面问题。如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
饿汉式单例类
1 public class EagerSingleton{ 2 private static EagerSingleton instance = new EagerSingleton(); 3 /** 4 *私有默认构造 5 */ 6 private EagerSingleton(){} 7 //静态工厂方法 8 public static EagerSingleton getInstance(){ 9 return instance; 10 } 11 }
饿汉式单例模式,会在这个类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,这时候,实例就被创建出来了,除非系统重启,否则这个对象不会改变,所以本身就是线程安全的。
饿汉式是典型的空间换时间,类加载的时候就创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。
懒汉式单例类
public class LazySingleton{ private static LazySingleton instance = null; //私有默认构造函数 private LaySingleton(){} //静态工厂方法 public static synchronized LazySingleton getInstance(){ if(instance == null){ instance = new LazySingleton(); } return instance; } }
懒汉式单例类对静态工厂方法使用了同步化,以处理多线程环境,此方式虽然解决了同步问题,但是该方式效率比较低下,下一个线程想要获取对象,就必须等上一个线程释放锁之后,才可以去获取。
懒汉式会在需要使用实例的时候才创建实例。
懒汉式是典型的时间换空间,就是每次实例都会判断,看是否需要创建实例,浪费了判断的时间,当然,如果一直没有人使用的话,就不会创建实例,节约了内存空间(但是没用,你建它干嘛)。
双重检验加锁模式
public class Sington{ private volatile static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ //先检查实例是否存在,如果不存在才进入下面的同步块 if(instance == null){ synchronized(Singleton.class){ //再次检查实例是否存在,如果不存在再创建实例 if(instance == null){ instance = new Singleton(); } } } return instance; } }
这种模式既可以保证线程安全的创建实例,而又不会对性能造成太大的影响。只会在第一次创建的时候同步,以后就都不会同步了,从而加快了运行速度。
提示:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行的效率不会很高。
静态内部类模式
这种模式综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙的同时实现了延迟加载和线程安全。
相应的基础知识:
❤ 什么是类级内部类?
简单来说,类级内部类就是有static修饰的成员内部类,如果没有sattic修饰的成员内部类被称为对象级内部类。
类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可以直接创建。而对象级的内部类的实例,是绑定在外部对象实例中的。
类级内部类中,可以定义静态的方法,在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
内级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载。
❤ 多线程缺省同步锁知识
在多线程开发中,为了解决并发问题,主要是通过加锁的方式来进行同步控制,但是在一些情况中,JVM已经隐含地为你执行了同步,这些情况下就不用自己再来进行同步控制了,这些情况包括:
1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
2.访问final字段时
3.在创建线程之前创建对象时
4.线程可以看见它将要处理的对象时
public class InnerSingleton { //私有构造方法 private InnerSingleton(){ } /** * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系, * 而且只有被调用时才会被装载,从而实现了延迟加载 */ private static class InnerInSingleton{ //静态的初始化器,由jvm来保证线程安全 private static InnerSingleton singleton = new InnerSingleton(); } //调用实例 public static InnerSingleton getSingleton(){ return InnerInSingleton.singleton; } }
当getInstance方法第一次被调用的时候,它第一次读取InnerInSingleton.singleton,导致InnerInSingleton类得到初始化;这种方式是Singleton类被装载了,singleton实例不一定被初始化。因为InnerInSingleton类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载InnerInSingleton类,从而实例化singleton。而这个类在装载并初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全。
这种模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
以上四种单例模式,并不能严格保证全局只有一个对象。
可以通过反射机制,设置AccessibleObject.setAccexxible(true),改变构造器的访问属性,调用构造器生成新的实例。
例1: 以调用内部静态类的单例模式为例
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { InnerSingleton singleton = InnerSingleton.getSingleton(); //getDeclaredConstructor:返回参数类型的所有构造器,包括public的和非public的,当然也包括private的。 Constructorconstructor = InnerSingleton.class.getDeclaredConstructor(); constructor.setAccessible(true); InnerSingleton singleton2 = constructor.newInstance(); System.out.println(singleton == singleton2); }
输出:
false
表明并不是同一个实例。
要防止这种情况,可以修改构造器,在第二次创建实例的时候抛出异常。
例2:还是以内部静态类的单例模式为例:
public class InnerSingleton { //定义count为全局静态变量 private static int count = 0; //私有构造方法 private InnerSingleton(){ if(count > 0){ throw new IllegalArgumentException("不能创造InnerSingleton两次!"); } count++; } /** * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系, * 而且只有被调用时才会被装载,从而实现了延迟加载 */ private static class InnerInSingleton{ //静态的初始化器,由jvm来保证线程安全 private static InnerSingleton singleton = new InnerSingleton(); } //调用实例 public static InnerSingleton getSingleton(){ return InnerInSingleton.singleton; } }
再运行用例1 的代码,则会报错:
Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.qrTest.singleton.Test.main(Test.java:14) Caused by: java.lang.IllegalArgumentException: 不能创造InnerSingleton两次! at com.qrTest.singleton.InnerSingleton.(InnerSingleton.java:10) ... 5 more
解决了多次实例化的问题。
当单例模式需要序列化的时候,新的问题又来了;
上面的几种单例模式都是可以序列化的,实现Serializable就可以实现序列化,为了保证序列化的时候实例还是Singleton,必须声明所有的实例域都是transient的,并且提供 readResolve方法,否则,每次反序列化都会生成新的实例。
例3:以内部静态类单例模式为例
public class InnerSingleton implements Serializable{ //私有构造方法 private InnerSingleton(){ } /** * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系, * 而且只有被调用时才会被装载,从而实现了延迟加载 */ private static class InnerInSingleton{ //静态的初始化器,由jvm来保证线程安全 private static InnerSingleton singleton = new InnerSingleton(); } //调用实例 public static InnerSingleton getSingleton(){ return InnerInSingleton.singleton; } //测试 public static void main(String[] args) throws Exception, IOException { InnerSingleton singleton = InnerSingleton.getSingleton(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser")); oos.writeObject(singleton); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser")); InnerSingleton singleton2 = (InnerSingleton) ois.readObject(); ois.close(); System.out.println(singleton == singleton2); } }
输出:
false
加入 readResolve 方法后:
public class InnerSingleton implements Serializable{ //私有构造方法 private InnerSingleton(){ } /** * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系, * 而且只有被调用时才会被装载,从而实现了延迟加载 */ private static class InnerInSingleton{ //静态的初始化器,由jvm来保证线程安全 private static InnerSingleton singleton = new InnerSingleton(); } //调用实例 public static InnerSingleton getSingleton(){ return InnerInSingleton.singleton; } //加入readResolve方法 public Object readResolve() { return InnerSingleton.getSingleton(); } //测试 public static void main(String[] args) throws Exception, IOException { InnerSingleton singleton = InnerSingleton.getSingleton(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser")); oos.writeObject(singleton); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser")); InnerSingleton singleton2 = (InnerSingleton) ois.readObject(); ois.close(); System.out.println(singleton == singleton2); } }
输出:
true
通过此方法保证了反序列化单例的唯一。
单例类和枚举
单元素的枚举类型已经成为Singleton的最佳方法。
public enum Singleton{ //定义一个枚举元素,它就代表了Singleton的一个实例 Instance; //单例也可以有自己的操作 public void singletonOpt(){ //操作 } }
使用枚举来实现单实例的控制会更加的简洁,而且无偿的提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化;
枚举类型也可以防止反射攻击,当你试图通过反射去实例化一个枚举类型的时候抛出IllegalArgumentException:Cannot reflectively create enum objects 异常,是更加简洁、高效、安全的实现单例的方式。
此篇文章参考:https://www.cnblogs.com/java-my-life/archive/2012/03/31/2425631.html,LZ个人觉得这个博主的java设计模式图文并茂讲解的非常好;欢迎大家去阅读。