设计模式系列:单例模式

一.名称

确保一个类只有一个实例,而且自行实例化,并向整个系统提供这个实例。
单例模式没有用到什么设计原则,更多的是一种封装的体现。

二.问题(为了解决什么问题)

  1. 要求生成唯一序列号的环境
  2. 在整个项目中需要一个共享访问点或共享数据,例如一个web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的
  3. 创建一个对象需要消耗的资源过多,如要访问io和数据库资源等
  4. 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。

三.解决方案(主要体现在uml和核心代码上)

单例的几种写法

1.饿汉式写法,线程安全

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {

    }

    public static EagerSingleton getInstance() {
        return instance;
    }
}

缺点:不能实现延迟加载

2.懒汉式写法,线程不安全

public class LazySingleton {
    private volatile static LazySingleton instance = null;

    private LazySingleton() {

    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

缺点:线程不安全

3.线程安全的单例:在getInstance方法上加同步

public static synchronized Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
}  

缺点:锁定的区域过大

4.线程安全的单例:双重检查锁定

public class LazySingleton {
    private volatile static LazySingleton instance = null;

    private LazySingleton() {

    }

    public static LazySingleton getInstance() {
        // 第一重判断
        if (instance == null) {
            // 锁定代码块
            synchronized (LazySingleton.class) {
                // 第二重判断
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

5.线程安全的单例:静态内部类

public class Singleton {    
    private static class LazyHolder {    
       private static final Singleton instance = new Singleton();    
    }    
    private Singleton (){}    
    public static final Singleton getInstance() {    
       return LazyHolder.instance;    
    }    
} 

这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。

6.有上限的多例模式(来自设计模式之禅)

如果只需要一个对象,使用单例模式就可以了,但是如果要求一个类只能产生两三个对象呢?就是这里讲的这种模式。
假设现在有两个皇帝(其实,历史上也是有的,明朝历史上的土木堡事变),代码如下:

public class Emperor {
    private static int MAXNUMOFEMPEROR = 2;
    private static ArrayList emperors = new ArrayList<>();

    static {
        for (int i = 0; i < MAXNUMOFEMPEROR; i++) {
            emperors.add(new Emperor("皇帝" + i));
        }
    }

    private Emperor(String name) {

    }

    public static Emperor getInstance() {
        Random random = new Random();
        int nextInt = random.nextInt(MAXNUMOFEMPEROR);
        return emperors.get(nextInt);
    }

}

四.效果(有啥优缺点)

优点:
1. 单例对象在内存中只有一个,可以减少内存开支,不过在java中要注意垃圾回收机制的影响。
2. 单例模式提供了对唯一示例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
3. 允许可变数目实例。

缺点:
1. 单例模式中没有抽象层,因此扩展困难,若要扩展,除非修改代码,基本上没有第二种途径。
2. 现在很多面对对象语言中都有垃圾回收机制,因此如果实例化的共享对象长时间不被使用,系统会认为他是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例状态的丢失。

常见案例

windows系统中的任务管理器无论启动多少次,都是唯一的。

负载均衡器。xx公司承接了一个服务器负载均衡软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量分发到服务器集群中的多台设备上进行并发处理,提高系统的整体处理能力,缩短响应时间。由于集群中的服务器需要动态删减,且客户端请求需要统一分发,因此需要确保负载均衡器的唯一性,即只能有一个负载均衡器来负责服务器的管理和请求的分发,否则将会带来服务器状态的不一致以及请求分配冲突等问题。

数据库连接池。xxx公司开发人员欲创建一个数据库连接池,将指定个数的(如2个或3个)数据库连接对象存储在连接池中,客户端代码可以从池中随机取一个连接对象来连接数据库。试通过对单例类进行改造,设计一个能够自行提供指定个数实例对象的数据库连接类。

你可能感兴趣的:(设计模式/重构/UML建模,设计模式实战)