设计模式之单例模式
来源:互联网 发布:知乎 恐怖 编辑:程序博客网 时间:2024/05/29 03:22
根据《设计模式的艺术------软件开发人员内功修养之道》一书作者刘伟老师的观点,对于软件开发人员来说,c、c++、java语言、开发环境等等都是一些招式,而内功则是数据结构、算法、设计模式、软件工程等等。由此可见,想成为高手,学习设计模式(也就是深厚的内功)是必不可少的。
经典的23种设计模式可分为三类:创建型模式,结构型模式,行为型模式。
本文就介绍创建型模式的第一种模式:单例模式,由此作为23种设计模式的开始。套用作者刘伟老师的一句话,“从它开始为大家逐一展现设计模式的魅力”。哈哈
单例模式可以用于系统中只需要创建一个对象的类,比如windows下的任务管理器。单例模式可以确保对象的唯一性。
单例模式的定义如下:确保某个类只有一个实例,而且自行实例化并向系统提供这个实例,这个类成为单例类,它提供全局访问的方法。
以下是一个典型的单例模式的例子。
- class TaskManager{
- private static TaskManager tm;
- private TaskManager(){
- }
- public static TaskManager getInstance(){
- if(tm == null)
- tm = new TaskManager();
- return tm;
- }
- }
以下是单例模式的一个应用。
- /*
- * 负载均衡器是一个单例类,用于处理并发请求时将请求分派给各个服务器
- * */
- class LoadBalancer{
- private static LoadBalancer lb;
- private Vector<String> serverlist;
- private LoadBalancer(){
- serverlist = new Vector<String>();
- }
- public static LoadBalancer getLoadBalancer(){
- if(lb == null){
- lb = new LoadBalancer();
- }
- return lb;
- }
- /*
- * 获取一个服务器用于处理请求
- * */
- public String getServer(){
- return serverlist.get(new Random().nextInt(serverlist.size()));
- }
- /*
- * 添加一个服务器
- * */
- public void addServer(String server){
- serverlist.add(server);
- }
- public void removeServer(String server){
- serverlist.remove(server);
- }
- }
注释讲的还算清楚吧。接下来是这个单例类的测试代码:
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- LoadBalancer lb1 = LoadBalancer.getLoadBalancer();
- LoadBalancer lb2 = LoadBalancer.getLoadBalancer();
- System.out.println("lb1==lb2?"+(lb1==lb2));//测试取得的是否为同一个对象
- lb1.addServer("server1");
- lb2.addServer("server2");
- lb1.addServer("server3");
- lb2.addServer("server4");
- for(int i = 0;i < 20;i++){
- String server = lb1.getServer();
- System.out.println("请求分发至:"+server);
- }
- }
- /*<span style="white-space:pre"> </span>输出:
- * <span style="white-space:pre"> </span>lb1==lb2?true
- 请求分发至:server1
- 请求分发至:server3
- 请求分发至:server1
- 请求分发至:server2
- 请求分发至:server4
- 请求分发至:server3
- 请求分发至:server3
- 请求分发至:server2
- 请求分发至:server2
- 请求分发至:server3
- 请求分发至:server2
- 请求分发至:server4
- 请求分发至:server1
- 请求分发至:server4
- 请求分发至:server4
- 请求分发至:server3
- 请求分发至:server1
- 请求分发至:server3
- 请求分发至:server2
- 请求分发至:server3
- * */
其实,以上的代码还是存在问题的,如:在多线程的情况下,当A线程调用getLoadBalancer()时,执行到new LoadBalancer()时由于创建工作比较耗时导致B线程对lb静态变量做出判断时仍然为null,此后A和B线程便都创建了一个LoadBalancer对象。此时,JVM中就有了两个LoadBalancer对象,那么LoadBalancer也就不在是单例类了。
那么,为了防止多线程的同步,可以使用synchronized关键字,getLoadBalancer()代码改为:
- public static synchronized LoadBalancer getLoadBalancer(){
- if(lb == null){
- lb = new LoadBalancer();
- }
- return lb;
- }
- public static synchronized LoadBalancer getLoadBalancerImprove(){
- if(lb == null){
- synchronized(LoadBalancer.class){
- if(lb == null)
- lb = new LoadBalancer();
- }
- }
- return lb;
- }
需注意,这里要进行两次的null判断,道理和上面的同步差不多。另外,由于两次的null判断,所以需要将lb静态变量加个volatile关键字,阻止编译器代码优化。这样的设计叫做双重检查锁定。
以上讨论有一个共同点,那就是单例对象的创建是在需要使用时才创建的,也就是懒汉式单例了,或者说饱汉式单例。相对应的,则是饿汉式单例了:
- class LoadBalancer{
- private static LoadBalancer lb = new LoadBalancer();
- private Vector<String> serverlist;
- private LoadBalancer(){
- serverlist = new Vector<String>();
- }
- public static LoadBalancer getLoadBalancer(){
- if(lb == null){
- lb = new LoadBalancer();
- }
- return lb;
- }
- /*
- * 获取一个服务器用于处理请求
- * */
- public String getServer(){
- return serverlist.get(new Random().nextInt(serverlist.size()));
- }
- /*
- * 添加一个服务器
- * */
- public void addServer(String server){
- serverlist.add(server);
- }
- public void removeServer(String server){
- serverlist.remove(server);
- }
- }
以上两种方式实现单例模式各有优缺点,一是同步,一是加载时间。那么有没有方式可以既解决同步的问题,又可以在加载时不创建对象,直到调用getLoadBalancer()时才创建呢?是的,有的。这就是IoDH(Initialization on Demand Holder)技术了,且看代码:
- class LoadBalancer{
- private static LoadBalancer lb;
- private Vector<String> serverlist;
- private static class HolderClass{
- private final static LoadBalancer instance = new LoadBalancer();
- }
- private LoadBalancer(){
- serverlist = new Vector<String>();
- }
- public static synchronized LoadBalancer getLoadBalancer(){
- return HolderClass.instance;
- }
- /*
- * 获取一个服务器用于处理请求
- * */
- public String getServer(){
- return serverlist.get(new Random().nextInt(serverlist.size()));
- }
- /*
- * 添加一个服务器
- * */
- public void addServer(String server){
- serverlist.add(server);
- }
- public void removeServer(String server){
- serverlist.remove(server);
- }
- }
IoDH技术也是有缺陷的,它需要语言的支持。啊,缺陷真是无穷无尽,从头到尾都没有一个解决方案是完美的。可见,世间之事,难有十全十美。
讨论了这么多,也该对单例模式做一个思考与总结了。
首先,需要注意的是,单例到底是在哪个范围内只有一个对象。想了想,对于java,我的回答是,在JVM中单例类只有一个实例。由于构造方法是私有的,所以反射是完不成单例对象的创建了。另外,类加载器的双亲委托机制使得即使用不同的类加载器来加载单例类,最终也是只加载一次该单例类,而不会多次加载。
- 设计模式之 单例设计模式
- 设计模式之 单例设计模式
- 设计模式之单例设计模式
- 设计模式之-----------单例设计模式
- 设计模式之:单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之-单例设计模式
- 设计模式之单例设计模式 标签: 设计模式
- 设计模式之单例
- 设计模式之单例
- 设计模式之 单例
- 两个日期相关方法
- 排序算法(二)简单选择排序
- 我终于找见那11道题的答案了
- $( "*", document.body)的含义
- 博客搬家
- 设计模式之单例模式
- apache 站点日志设置总结
- 阿里巴巴2014实习面试经历
- 汇编 .S 转为.s 方式
- leetcode之Binary Tree Zigzag Level Order Traversal
- 一个简单的E_mail地址判断函数
- POJ 1185 炮兵阵地(状态压缩DP)
- “五一”劳动节寄语
- Linux下查看命令的位置