首页 > 代码库 > 设计模式(一)单例模式

设计模式(一)单例模式

单例模式:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。


 

优点: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 true20 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 true26 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                 @Override12                 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 }

经过测试 懒汉式耗时最多,其次是双重检查锁式,其他相差不大。

设计模式(一)单例模式