JAVA中常用的几种设计模式--单例

前段时间面试的时候被问到了设计模式,结果想想只了解单例、工厂…囧,所以整理下,温故而知新。
设计模式:简单说就是前人留下的一些经验,有助于提高代码的复用率,增加可读性;
单例模式应该是使用比较多的模式之一,很多人都是一知半解,其中也包括我,哈哈

单例模式

定义:确保一个类只有一个实例,并且提供一个全局访问点;
优点:该类的实例只有一个;全局共享;缩短对象的创建时间;

七种写法

单例模式的各种写法其实有利有弊,简单分析下

饿汉模式
public class Singleton {  
     private static Singleton instance = new Singleton();  
     private Singleton (){
     }
     public static Singleton getInstance() {  
         return instance;  
     }  
 }

在类加载的时候就创建,以空间换时间的做法。

java中的runtime 类就是采用的饿汉模式

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

    //以下代码省略
}

总结:这种方式简单粗暴,对于占用内存少,加载时间短的部分单例可以使用这种方式。但是对于启动时间有要求,占用内存资源较多的类 不建议这么写。

懒汉模式
  • 非线程安全

    public class Singleton {  
      private static Singleton instance;  
      private Singleton (){
      }   
      public static Singleton getInstance() {  
          if (instance == null) {  
              instance = new Singleton();  
          }  
          return instance;  
      }  
    }

    懒汉模式申明了一个静态对象,在第一次加载的时候会实例化。延时加载了,但是是非线程安全的,可能创建多个对象实例。

    • 线程安全
public class Singleton {  
      private static Singleton instance;  
      private Singleton (){
      }
      public static synchronized Singleton getInstance() {  
          if (instance == null) {  
              instance = new Singleton();  
          }  
          return instance;  
      }  
 }

因为这种方式在getInstance()方法上加了同步锁,所以在多线程情况下会造成线程阻塞,把大量的线程锁在外面,只有一个线程执行完毕才会执行下一个线程。因为锁住getInstance 导致执行的效率比较的低;

  • 线程安全,双重校验锁

public class Singleton {

    /**
     * 注意此处使用的关键字 volatile,
     * 被volatile修饰的变量的值,将不会被本地线程缓存,
     * 所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
     */
    private volatile static Singleton singleton;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return singleton;
    }
}

这种写法在getSingleton()方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是在singleton等于null的情况下才创建实例。
双重校验锁:既可以达到线程安全,也可以使性能不受很大的影响,换句话说在保证线程安全的前提下,既节省空间也节省了时间,集合了「饿汉式」和两种「懒汉式」的优点,取其精华,去其槽粕。

需要说明的是: 对于volatile关键字,还是存在很多争议的。由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。
还有就是在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只只能用在java1.5及以上的版本。

  • 静态内部类

很多时候,java都给我们提供了同步的控制;
比如:
static{…}区块初始化数据的时候。
final变量被访问的时候。

因为在JVM进行类加载的时候他会保证数据是同步的,我们可以这样实现:采用内部类,在这个内部类里面去创建对象实例。这样的话,只要应用中不使用内部类 JVM 就不会去加载这个单例类,也就不会创建单例对象,从而实现「懒汉式」的延迟加载和线程安全。

public class Singleton { 
    private Singleton(){
    }
      public static Singleton getInstance(){  
        return SingletonHolder.sInstance;  
    }  
    private static class SingletonHolder {  
        private static final Singleton sInstance = new Singleton();  
    }  
}
  • 枚举

《Java与模式》中,作者这样写道,使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。

public enum Singleton {
     //定义一个枚举的元素,它就是 Singleton 的一个实例
     INSTANCE;  

     public void doSomeThing() {  
         // do something...
     }  
 }

使用方式:

public static void main(String args[]) {
    Singleton singleton = Singleton.instance;
    singleton.doSomeThing();
}

实现简单,但是可读性比较的差

  • 使用容器
public class SingletonManager { 
  private static Map<String, Object> objMap = new HashMap<String,Object>();
  private Singleton() { 
  }
  public static void registerService(String key, Objectinstance) {
    if (!objMap.containsKey(key) ) {
      objMap.put(key, instance) ;
    }
  }
  public static ObjectgetService(String key) {
    return objMap.get(key) ;
  }
}

这种事用SingletonManager 将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

总结:
对于以上七种单例,分别是「饿汉式」、「懒汉式(非线程安全)」、「懒汉式(线程安全)」、「双重校验锁」、「静态内部类」、「枚举」和「容器类管理」。很多时候取决人个人的喜好,虽然双重检查有一定的弊端和问题,但我就是钟爱双重检查,觉得这种方式可读性高、安全、优雅(个人观点)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值