设计模式单例模式
Posted 灌水乐园
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式单例模式相关的知识,希望对你有一定的参考价值。
单例模式:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
优点:1. 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时产生一个单例对象,然后永久驻留内存的方式来解决。
2. 单例模式可以在系统设置全局的访问点,例如可以设计一个单例类,负责所有数据表的映射处理。
常见的五种单例模式实现方式:
饿汉式:线程安全,调用效率高。但是,不能延时加载。
懒汉式:线程安全,调用效率不高。但是,可以延时加载。
双重检测锁式:由于JVM底层内部模型原因,偶尔会出现问题,不建议使用。
静态内部类式:线程安全,调用效率高。但是,可以延时加载。
枚举单例:线程安全,调用效率高,不能延时加载。
饿汉式:
1 public class Singleton{ 2 private static Singleton singleton = new Singleton(); 3 private Singleton(){} 4 public static Singleton getInstance(){ 5 return singleton; 6 } 7 }
懒汉式:
1 public class Singleton{ 2 private static Singleton singleton; 3 private Singleton(){} 4 public synchronized static Singleton getInstance(){ 5 if(singleton == null){ 6 singleton = new Singleton(); 7 } 8 return singleton; 9 } 10 }
双重检测锁式:
1 public class Singleton{ 2 private static Singleton singleton; 3 private Singleton(){} 4 public static Singleton getInstance(){ 5 if(singleton == null){ 6 synchronized(Singleton.class){ 7 if(singleton == null){ 8 singleton = new Singleton(); 9 } 10 } 11 } 12 return singleton; 13 } 14 }
静态内部类式:
1 public class Singleton{ 2 private static class SingletonClassInstance{ 3 private static final Singleton singleton = new Singleton(); 4 } 5 public static Singleton getInstance(){ 6 return SingletonClassInstance.singleton; 7 } 8 private Singleton(){} 9 }
注:外部类没有static属性,则不会像饿汉式那样立即加载对象。只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。singleton是static final(final可省略)类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。兼备了并发高效调用和延迟加载的优势。
枚举单例:
1 public enum Singleton{ 2 //定义一个枚举的元素,它就代表了Singleton的一个实例 3 INSTANCE; 4 public void singletonOperation(){ 5 //功能处理 6 } 7 } 8 9 public class GOF { 10 public static void main(String[] args){ 11 Singleton s1 = Singleton.INSTANCE; 12 Singleton s2 = Singleton.INSTANCE; 13 System.out.println(s1==s2); 14 } 15 } 16 17 打印结果:true
注:实现简单。枚举本身就是单例模式。由JVM从根本上提供保障,避免通过反射和反序列化的漏洞。但无延迟加载。
五种如何选用:
单例对象 占用资源少,不需要延时加载:枚举式好于饿汉式
单例对象 占用资源大,需要延时加载:静态内部类好于懒汉式
问题:
反射和反序列化可以破解上面几种(不包含枚举式)实现方式。
1. 例:通过反射的方式直接调用私有构造器。
代码中的SingletonDemo1为饿汉式
1 import java.lang.reflect.Constructor; 2 3 public class SingletonDemo1_1 { 4 public static void main(String[] args)throws Exception{ 5 SingletonDemo1 s1 = SingletonDemo1.getInstance(); 6 SingletonDemo1 s2 = SingletonDemo1.getInstance(); 7 System.out.println(s1 == s2); 8 9 Class<SingletonDemo1> clazz = (Class<SingletonDemo1>)SingletonDemo1.class; 10 Constructor<SingletonDemo1> c = clazz.getDeclaredConstructor(null); 11 c.setAccessible(true); 12 SingletonDemo1 s3 = c.newInstance(); 13 SingletonDemo1 s4 = c.newInstance(); 14 System.out.println(s3 == s4); 15 } 16 } 17 18 打印结果: 19 true 20 false
解决方法:可以在构造方法中抛出异常控制。
修改后的饿汉式为:
1 public class SingletonDemo1{ 2 private static SingletonDemo1 single = new SingletonDemo1(); 3 private SingletonDemo1(){ 4 if(single != null){ 5 throw new RuntimeException(); 6 } 7 } 8 public static SingletonDemo1 getInstance(){ 9 return single; 10 } 11 }
2. 例:通过反序列化的方式构造多个对象。
1 import java.io.FileInputStream; 2 import java.io.FileOutputStream; 3 import java.io.ObjectInputStream; 4 import java.io.ObjectOutputStream; 5 6 public class SingletonDemo1_1 { 7 public static void main(String[] args)throws Exception{ 8 SingletonDemo1 s1 = SingletonDemo1.getInstance(); 9 SingletonDemo1 s2 = SingletonDemo1.getInstance(); 10 System.out.println(s1 == s2); 11 12 FileOutputStream fos = new FileOutputStream("G:/a.txt"); 13 ObjectOutputStream oos = new ObjectOutputStream(fos); 14 oos.writeObject(s1); 15 oos.close(); 16 fos.close(); 17 18 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("G:/a.txt")); 19 SingletonDemo1 s3 = (SingletonDemo1)ois.readObject(); 20 System.out.println(s2 == s3); 21 } 22 } 23 24 打印结果: 25 true 26 false
解决方法:定义readResolve()方法,则直接返回此方法指定的对象,而不需要单独再创建新对象。
如:
1 import java.io.ObjectStreamException; 2 import java.io.Serializable; 3 4 public class SingletonDemo1 implements Serializable { 5 private static SingletonDemo1 single = new SingletonDemo1(); 6 private SingletonDemo1(){ 7 if(single != null){ 8 throw new RuntimeException(); 9 } 10 } 11 public static SingletonDemo1 getInstance(){ 12 return single; 13 } 14 private Object readResolve() throws ObjectStreamException{ 15 return single; 16 } 17 }
五种单例模式在多线程环境下的效率测试:
CountDownLatch同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
countDown() 当前线程调此方法,则计数减一(建议放在finally里执行)
await() 调用此方法会一直阻塞当前线程,直到计时器的值为0
1 import java.util.concurrent.CountDownLatch; 2 3 4 public class Cilent { 5 public static void main(String[] args)throws Exception{ 6 long start = System.currentTimeMillis(); 7 int threadNum = 10; 8 final CountDownLatch countDownLatch = new CountDownLatch(threadNum); 9 for(int i = 0; i < 10; i++){ 10 new Thread(new Runnable(){ 11 @Override 12 public void run() { 13 for(int i = 0; i < 100000; i++){ 14 Object o = SingletonDemo1.getInstance(); 15 } 16 countDownLatch.countDown(); 17 } 18 }).start(); 19 } 20 countDownLatch.await(); 21 long end = System.currentTimeMillis(); 22 System.out.println("总耗时 = " + (end - start)); 23 } 24 }
经过测试 懒汉式耗时最多,其次是双重检查锁式,其他相差不大。
以上是关于设计模式单例模式的主要内容,如果未能解决你的问题,请参考以下文章