最简单的设计模式--单例模式

  我看的设计模式书是《Head First设计模式》,我决定不按照书上的章节顺序做笔记,按照我认为的容易理解程度从易到难来写。

  一、单例模式的定义

  单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。

  二、实例说明

  有一些对象其实我们只需要一个,例如:线程池(threadpool)、缓存(cache)、对话框、处理器偏好设置和注册表(registry)的对象、日志对象等。

  2.1 一个最简单的经典单例模式代码

  我们可以用Java的静态变量来坐到这一点,但事静态全局变量在程序一开始就被创建好对象,如果这个对象非常耗费资源,而程序在这次执行过程中又一次没用到它,就形成浪费。

经典的单例模式代码:

 1 public class Singleton {
 2     private static Singleton uniqueInstance;
 3     private Singleton(){}//构造器声明为私有,只有类内才可以调用构造器
 4     public static Singleton getInstance(){
 5         if(uniqueInstance==null){
 6             uniqueInstance=new Singleton();
 7         }
 8         return uniqueInstance;
 9     }
10 }

 

  别的类用要用这个类的对象的话,就通过Singleton.getInstance()来获取,if判断而且确保了这个类只有一个实例化的对象。这样似乎都一切正常了。不过实际项目中,肯定会有多线程的场景,那样就可能产出两个实例。例如这里有两个线程,线程1运行到上述代码第5行时候,new 一个对象,假如此刻线程2也进入5行,发现此时uniqueInstance为null,它也new 一个对象,那么就产生了两个实例化对象了。所以,要在第4行上面加上synchronized,这样就保证了不会存在两个线程同时进入到getInstance方法。

  2.2改善多线程

  在2.1中我们说过在getInstance方法加入synchronized关键字来解决多线程会实例化多个对象的问题,它也存在一些问题:

  1)同步会降低性能;

  2)更严重的是:上述代码只要第一次执行getInstance方法时,才真正需要同步。换句话说,一旦设置好uniqueInstance变量,就不再需要同步这个方法了。之后每次调用这个方法,同步都是一种累赘。

  下面给出解决三种解决方案:

  2.2.1 如果getInstance()的性能对应用程序不是很关键,就什么都别做

   没错,就是这么直接!当然,如果getInstance()在程序中频繁的运行,那就得重新设计了。

  2.2.2 使用“急切”创建实例,而不用延迟实例化的做法

  如果程序总是会创建并使用到这个单例类,或者创建这个单例类的实例不繁重,可以急切的创建此单例:

public class Singleton {
    private static Singleton uniqueInstance=new Singleton();//在静态初始化中创建单例
    private Singleton(){}
    public static Singleton getInstance(){
        return uniqueInstance;
    }
}

  2.2.3 用“双重检查加锁”,在getInstance()中减少使用同步

  首先检查是否实例已经创建了,如果没有,“才”进行同步。这样一来,只有第一次创建会用到同步,这正是我们想要的。

 1 public class Singleton {
 2     private volatile static Singleton uniqueInstance;
 3     private Singleton() {}
 4     public static Singleton getInstance() {
 5         if (uniqueInstance == null) {
 6             synchronized (Singleton.class){
 7                 if(uniqueInstance == null){//进入区域后,再检查一次,如果仍是null,才创建实例
 8                     uniqueInstance = new Singleton();
 9                 }
10             }
11         }
12         return uniqueInstance;
13     }
14 }

  第7行还需要加入一次判断,有可能别的线程在此线程拿5-6行的期间,已经实例化了。(ps:我的个人理解)关于volatile关键字,不清楚的同学可以去查,以后等我看完JVM,会专门写一篇volatile关键字的文章。

  三、单例模式的延伸

  1、听说两个类加载器可能会各自创建自己的单例对象?

  答:是的。每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的类加载器可能会同时加载同一个类,从整个程序来看,同一个类会被加载多次。如果这样的事情发生在单件上,就会产生多个单例并存的现象。所以,如果程序有个多个类加载器又同时使用了单例模式,可以这样解决:自行指定类加载器,并指定同一个类加载器。

  2、全局变量比单例模式差在哪里?

  答:1)不可延迟实例化;2)并不能确保只有一个实例,而且也变相鼓励开发人员,用许多全局变量指向许多小对象来造成命名空间的污染。

posted @ 2017-02-27 19:11  李子沫  阅读(1125)  评论(0编辑  收藏