Java中的单例模式

 · 4 mins read

什么是单例模式?

单例模式是一种对象创建模式,用于产生对象的一个具体实例,并且可以确保系统中一个类只能产生唯一的一个实例。

这样的方式能够带来两大好处:

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,对于复制的对象,这样的创建方式的开销是十分值得的;
  • new 的次数少了,对内存的使用频率也就下降了,当然也就减轻了 GC 的压力。

设计思想

这种模式要解决的问题就是要保证应用中只存在一个对象。那怎样实现呢?

  1. 不允许其他程序new对象
  2. 在该类中创建对象
  3. 对外需要暴露一个方法,让其他获取这个对象

体现到代码中,解决办法为:

  1. 私有化构造函数
  2. 在本类中创建一个本类对象
  3. 定义一个方法,能够让其他的类获取这个对象

Code

  1. 最简单的写法为

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

    这种方式称为饿汉式。

    • 优点
      • 简单明了
      • 在类加载时即完成了实例化,避免了线程同步的问题
    • 缺点
      • 没有使用懒加载
      • 可能造成内存浪费
  2. 变种方式

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

    相比于第一种写法,本类中将实例化对象的代码放到了 static 代码块中。两种方法的效果其实是一样的。所以优缺点也是一样的。

  3. 懒汉式(线程不安全)

     public class Singleton {  
    	
         private static Singleton instance;    
    	    
         private Singleton (){
         }    
    	    
         public static Singleton getInstance() {    
             if (instance == null) {        
                 instance = new Singleton();    
             }
            return instance;
         }
     }
    
    • 优点
      • 使用了懒加载
    • 缺点
      • 当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
  4. 懒汉式(线程安全)

     public class Singleton { 
    	 
         private static Singleton instance;    
    	    
         private Singleton (){
         }    
    	    
         public static synchronized Singleton getInstance(){    
           if (instance == null) {
                instance = new Singleton();    
           }
           return instance;
         }
     }
    
    • 优点
      • 线程安全,解决了多实例问题
    • 缺点
      • 不高效。因为在任何时候只能有一个线程调用 getInstance() 方 法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。
  5. 双重校验锁

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

    进行两次 if(instance == null) (Double-Check)的检查可以保证线程安全。

    • 优点
      • 并发度高,提升了性能。
    • 缺点
      • 并不是绝对安全
  6. 内部类

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

    这是Google公司的工程师Bob Lee写的新的懒汉单例模式。

    • 优点
      • 使用JVM本身机制保证了线程安全问题
      • 由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的。
    • 同时读取实例的时候不会进行同步,没有性能缺陷。
    • 不依赖jdk版本。

    这种方式也是《Effective Java》推荐的写法。

  7. 枚举

     public enum EasySingleton{
         INSTANCE;
     }
    
    • 优点
      • 简单明了
      • 调用简单,使用EasySingleton.INSTANCE访问实例,比getInstance()简单
      • 创建枚举本身就是线程安全的,不用担心存在多个实例的问题
    • 缺点
      • 枚举类型enum在jdk1.5时才引入,所以这种方法并不适用于jdk1.5之前的版本

测试

使用多线程并发测试。

public class SingletonPatternTest {

    public static void main(String[] args){
        CountDownLatch latch = new CountDownLatch(1);
        int threadCount = 1000;
        for (int i = 0; i < threadCount; i++){
            new Thread(){
                
				@Override
                public void run() {
                    try {
                        // all thread to wait
                        latch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // test get instance
                    System.out.println(Singleton.getInstance().hashCode());
                }
            }.start();
        }
        // release lock, let all thread excute Singleton.getInstance() at the same time
        latch.countDown();
    }
}

其中 CountDownLatch latch 为闭锁,所有线程中都用 latch.await() ;等待锁释放,待所有线程初始化完成使用 latch.countDown() ; 释放锁,从而达到线程并发执行 Singleton.getInstance() 的效果。

结果为:

2016228077
2016228077
2016228077
2016228077
...

Reference:

http://blog.csdn.net/dmk877/article/details/50311791

http://www.trinea.cn/java/singleton/