首页 > 代码库 > 设计模式(一)单例模式
设计模式(一)单例模式
单例模式:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
优点: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 }
经过测试 懒汉式耗时最多,其次是双重检查锁式,其他相差不大。
设计模式(一)单例模式