设计模式单例模式

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 }

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

以上是关于设计模式单例模式的主要内容,如果未能解决你的问题,请参考以下文章

常用代码片段

性能比较好的单例写法

你熟悉的设计模式都有哪些?写出单例模式的实现代码

单例模式以及静态代码块

设计模式之单例模式

设计模式之单例模式