设计模式_单例模式



在分享今天的笔记之前,我得先澄清一下。关于设计模式的学习,我都是看刘伟博士 博客上的资料,因为关于Java实现的设计模式的资料比较少,而刘伟博士整理的又非常的好,不得不膜拜。

学习知识最好的方法,就是自己去尝试,然后自己整理出来,跟别人分享,一旦你能够分享出来了,就真的掌握了知识。所以我用这种方式,学习其他博客的东西,整理出来,自己组织语言分享出来,这样是最高效的。所以,我有摘取其他博客的东西,但是也会参杂许多自己的理解,请广大学习者海涵。

前三节我分享了三种不同工厂模式的笔记,分别是简单工厂模式、工厂方法模式和抽象工厂模式,它们都有不同的使用范围,有不同的优势劣势。

今天我们继续来学习另一种对象创建的模式——单例模式。单例模式,说到底就是要确保对象的唯一性。

p{font-size:20}
span{font-size:24;color:red}
h3{font-size:28;color:green}
strong{font-size:22;color:red}


概述



我们有时候会碰到这么一种情况,我们希望类只能创建一个唯一的对象,这个对象要对外隐藏,只对外提供一个访问的接口,这个很容易实现,把对象创建封装在内部,再private,接着写一个访问的方法就可以了。

就拿Windows下的任务管理器来说,在Windows系统下,我们通常都只能打开一个任务管理器,这个任务管理器具有唯一性。Windows系统下的任务管理器就是单例模式。

从以上的话你能抓到几点关键的东西?

单例模式实现有三个重要的点。

  1. 这个单例类只有一个示例;
  2. 这个单例类需要对外隐藏;
  3. 对全局提供访问唯一对象的接口。

看起来,实现单例类具体要解决的两个问题:

  1. 解决资源浪费的问题,如果一个电脑能好多好多个任务管理器,这意味着要有很多个实例;
  2. 解决一个时差问题,不同时期访问对象的状态是不一致的,如果不同的时刻去访问任务管理器,它们的信息有可能是不一样的,那还有什么意义吗?


初探



首先我们来实现简单的单例模式,由浅到深循环渐进。

import java.util.*;

class TaskManager {
    private static TaskManager tm = null;   //对外实现隐藏,保证自己具有唯一性

    private TaskManager()
    {
        //初始化窗口
        System.out.println("初始化窗口");
    }

    public void displayProcesses()
    {
        //显示进程
        System.out.println("显示进程");
    }

     public static TaskManager getInstance()  
     {  
        if (tm == null)  
        {  
            tm = new TaskManager();  
        }  
        return tm;  
    }  
}  

在这里,我们实现了三个重要的点。我们写了一个唯一的静态对象,并且实现了private(隐藏)。我们还对外提供了一个访问方法getInstance,可以拿到该对象。


遗难



现在有这么一个问题,在我们获取这个对象的方法(getInstance)里,我们增加了很多很多的代码。也就是在创建这个唯一的对象之前,我们还有一大堆代码要去执行。

这个时候,如果有多线程的存在,另一个地方也调用了这个方法。一检测,发现还没创建这个唯一的对象,又开始创建一个新的对象。那么,两个不同的线程,最终创建出了两个对象,这就有问题了。

如果不太清楚,我再举个例子。

public static TaskManager getInstance()  
{
    if (tm == null)  
    {  
        process();  
        tm = new TaskManager();  
    }  
    return tm;  
}  

当判断这个对象还没有创建之后,会调用一个process()方法,然后再创建对象。假设这个方法执行的时候需要1秒,而在这1秒之前,由于有多线程的存在,又调用了这个getInstance()方法。这个时候检测发现,还没有创建对象(因为还在执行process()方法),就创建了一个新的对象。

这样一来,就有两个对象了,对象的唯一性被破坏。那怎么解决呢?有三种解决方法,分别是饿汉式、懒汉式和Holder技术。


饿汉式



饿汉式是比较简单的,一开始就创建了该对象的示例,这样就不用去判断是否创建对象了。

import java.util.*;

class TaskManager {
    private static TaskManager tm = new TaskManager();  //对外实现隐藏,保证自己具有唯一性

    private TaskManager()
    {
        //初始化窗口
        System.out.println("初始化窗口");
    }

    public void displayProcesses()
    {
        //显示进程
        System.out.println("显示进程");
    }

     public static TaskManager getInstance()  
     {  
        return tm;  
    }  
}  

这个解决方案相对比较简单,我们继续往下看,看看懒汉式。


懒汉式



由于多线程的存在,所以会产生时间的差异,所以我们直接把进程”锁”起来,每一次都只允许一个线程调用该方法。

import java.util.*;

class TaskManager {
    private static TaskManager tm = null;   //对外实现隐藏,保证自己具有唯一性

    private TaskManager()
    {
        //初始化窗口
        System.out.println("初始化窗口");
    }

    public void displayProcesses()
    {
        //显示进程
        System.out.println("显示进程");
    }

     public static TaskManager getInstance()  
     {  
        if (tm == null)  
        {  
            synchronized (TaskManager.class) {  
                tm = new TaskManager();  
            }    
        }  
        return tm;  
    }  
}  

我们将对象创建的一小段代码给”锁”起来了,每一次都只有一个线程能执行。看上去貌似解决了,但其实还是存在问题的,也是刚刚的问题,两个线程都调用了这个方法,一判断,对象还没有创建,结果就排队创建对象,最终还是创建了两个对象。

那我们能不能直接把整个方法都”锁”起来?当然可以,但是这样做,程序的性能就会下降。那还能怎么办?我们能使用双重检测。

import java.util.*;

class TaskManager {
    private volatile static TaskManager tm = null;  //对外实现隐藏,保证自己具有唯一性

    private TaskManager()
    {
        //初始化窗口
        System.out.println("初始化窗口");
    }

    public void displayProcesses()
    {
        //显示进程
        System.out.println("显示进程");
    }

     public static TaskManager getInstance()  
     {  
        if (tm == null)  //第一重检测
        {  
            synchronized (TaskManager.class) {  
                if(tm == null)  //第二重检测
                {
                    tm = new TaskManager();                     
                }
            }    
        }  
        return tm;  
    }  
}  

这种方法确实可以,但是实在是“难看”,增加了代码量不说,还出现了重复的代码,这不是一种良好的编程风格。


两种方式的比较



饿汉模式看起来比懒汉模式简单,但是却有一个缺点。当我们加载TaskManager类时,就会自动创建这个对象。但是,有时候我们并不需要创建这个对象,这就造成了资源浪费的问题。

而懒汉模式,很”难看”,有重复的代码,同时也比较复杂。

有木有综合两者优势的解决方案呢?当然有——Holder技术。


Holder技术



import java.util.*;

class TaskManager {
    private volatile static TaskManager tm = null;  //对外实现隐藏,保证自己具有唯一性

    private TaskManager()
    {
        //初始化窗口
        System.out.println("初始化窗口");
    }

    public void displayProcesses()
    {
        //显示进程
        System.out.println("显示进程");
    }

    private static class HolderTaskManager{
        private final static TaskManager tm = new TaskManager();
    }

     public static TaskManager getInstance()  
     {   
        return HolderTaskManager.tm;  
    }  
}  

我们增加了一个静态内部类,当我们需要这个对象的时候,就会自动加载这个静态内部类HolderTaskManager,加载之后才会创建这个对象。这样我们就不会用到线程锁的知识,降低了程序的复杂度,同时,我们也不会浪费资源,解决了资源浪费的问题。皆大欢喜。


总结



单例模式,说到底就是确保对象的唯一性,在这个过程中,解决两个重要的问题。一个是时差引发的问题,另一个是资源浪费。通过Holder技术,我们可以同时解决这两个问题,这个模式就暂时到这里,以后如果还有什么需要补充的,我会继续更新。

2017/10/7 22:15:40 @Author:云都小生

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值