Java性能优化之设计模式-单例模式

来源:互联网 发布:徐氏八字软件 编辑:程序博客网 时间:2024/05/29 17:45

单例模式是一种常用的软件设计模式,常被用于一个系统中一个类只存在一个实例的场合,从而方便对实例个数的控制并节约系统资源。”简而言之,单例模式就是保证一个类最多只能存在一个实例对象。

使用场景:

1)对应于频繁使用的对象,可以省略创建对象所花费的时间,对于一些大型对象,是一笔可观的开销。

2)由于减少了new对象,减轻系统内存使用频率和GC开销。


单例模式必须要有private访问权限的构造函数,确保该类不会被其他代码实例化,其次是instance成员变量和getInstance方法必须是static,在实现方式上可以分成:懒汉模式和饿汉模式

懒汉模式:

顾名思义,lazy loading(延迟加载,一说懒加载),在需要的时候才创建单例对象,而不是随着软件系统的运行或者当类被加载器加载的时候就创建。当单例类的创建或者单例对象的存在会消耗比较多的资源,常常采用lazy loading策略。这样做的一个明显好处是提高了软件系统的效率,节约内存资源。下面我们看看最简单的懒汉单例模式:

public class Singleton {
  privatestatic Singleton instance =null;
  private Singleton() {
      System.out.print("private construct func...");
   }
  publicstatic Singleton getInstance() {
     if (instance ==null) {
       instance =new Singleton();
      }
     returninstance;
   }

}


饿汉模式:相对于懒汉模式,饿汉模式也有其不足之处,在类被加载时就创建单例对象并且长驻内存,不管你是否使用到它;如果Singleton类占用的资源比较多,势必会降低资源利用率以及程序运行效率。

public class Singleton {
  privatestatic Singleton instance =new Singleton();
  private Singleton() {
      System.out.print("private construct func...");
   }
  publicstatic Singleton getInstance() {
     returninstance;
   }

}

在单线程环境下,多次调用getInstance()方法获得的Singleton对象均为同一个对象。然而,实际应用环境中很多都会涉及到多线程,因此不得不考虑线程安全的问题。简单的懒汉式在多线程情况下,可能出现多个线程同时调用getInstance方法,此时都判断instance为null,每个线程都会创建多个Singleton实例,违背了单例模式的初衷。这说明懒汉式在多线程环境下不是线程安全的。于是想到在getInstance()方法上同步锁。

public class Singleton {
  privatestatic Singleton instance =null;
  private Singleton() {
      System.out.print("private construct func...");
   }
  publicstatic synchronized Singleton getInstance() {
     if (instance ==null) {
       instance =new Singleton();
      }
     return instance;
   }

}

虽然这种在方法前加上同步锁的方式可以解决多线程问题,但是在使用中通常只需在第一次创建对象,以后调用getInstance方法只需要返回已经实例化的Singleton对象即可,但是因为同步锁锁住整个方法可能粒度过大,不利于效率。既然锁方法不太好,那么锁代码呢?

public class Singleton {
  privatestatic Singleton instance =null;
  privatestatic Object lock =new Object();
  private Singleton() {
      System.out.print("private construct func...");
   }
  publicstatic Singleton getInstance() {
     if (instance ==null) {
       synchronized (lock) {
          instance =new Singleton();
        }
      }
     returninstance;
   }

}

在getInstance()方法里,在判空语句后上锁,判空处主要是为了性能考虑,如果对象已经实例化,直接返回,这样做看似解决了线程安全问题,其实不然。设现有线程a和b,在t1时刻线程a和b均已通过判空语句;t2时刻时,a先取得锁资源进入临界区(被锁的代码块),执行new操作实例instance对象,然后退出临界区。t3时刻,b进入临界区,执行new操作创建实例对象。很明显Singleton被实例化两次。仍不能保证线程安全。基于此,又提出了双重锁校验式。

public class Singleton {
  privatestatic Singleton instance =null;
  privatestatic Object lock =new Object();
  private Singleton() {
      System.out.print("private construct func...");
   }
  public static Singleton getInstance() {
     if (instance ==null) { //1
       synchronized (lock) {
          if (instance ==null) {
             instance =new Singleton(); //2
           }
        }
      }
     returninstance;
   }

}

在临界区内部再进行一次断空,解决了在临界区同时实例化对象的情况,看似解决了问题,但是仔细思考,双重锁还是存在问题。比如有线程a和b,a首先进入临界区在2处对instance进行初始化,同时b线程在外层1处判空,此时可能出现b线程返回一个只被ax线程部分初始化的instance对象,导致异常。于是提出了如下修改办法:

public class Singleton {
  privatestatic Singleton instance =null;
  privatestatic Object lock =new Object();
  private Singleton() {
      System.out.print("private construct func...");
   }
  publicstatic Singleton getInstance() {
     if (instance ==null) {
       synchronized (lock) {
           Singleton tmpInstance =instance;  // 使用临时变量
          if (tmpInstance ==null) {
              tmpInstance =new Singleton();
           }
          instance = tmpInstance;
        }
      }
     returninstance;
   }

}

或者:

public class Singleton {
  privatestatic volatile Singleton instance =null; //注意关键字
  privatestatic Object lock =new Object();
  private Singleton() {
      System.out.print("private construct func...");
   }
  publicstatic Singleton getInstance() {
     if (instance ==null) {
       synchronized (lock) {
          if (instance ==null) {
             instance =new Singleton();
           }
        }
      }
     returninstance;
   }

}

以上两种办法能很好的解决双重锁失效的问题,当然很多时候直接使用饿汉模式就能保证线程安全,而不必使用懒汉模式。

0 0
原创粉丝点击