单例模式

  单例模式(Singleton),是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。

  优点: 
      1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例。
      2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。 
      3.提供了对唯一实例的受控访问。 
      4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。 省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
      5.避免对共享资源的多重占用。 

      6.有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

  缺点:

   1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。 
      2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。 
      3.单例类的职责过重,在一定程度上违背了“单一职责原则”。 
      4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。 

  下面写一个最基础的单例模式:

 1 public class Singleton1 {
 2 
 3     //初始化一个实例对象
 4     private static Singleton1 sin = new Singleton1();
 5     
 6     /**
 7      * 私有构造器,防止被new出来
 8      */
 9     private Singleton1 (){
10     }
11     
12     /**
13      * 获取唯一实例的方法
14      * @return
15      */
16     public static Singleton1 getInstance(){
17         return sin;
18     }
19 }

  上面的代码中有一个缺点就是,这个类在加载的时候会直接new一个静态对象出来,当系统中这样的类比较多时,会使得项目启动非常慢。现在流行的设计都讲求“延迟加载”,我们可以在第一次使用的时候才初始化第一个该类对象,所以上面代码只适合在小程序。

  对上面代码进行优化:

 1 public class Singleton2 {
 2     //将此处改为null是为了懒加载
 3     private static Singleton2 sin = null;
 4         
 5     /**
 6      * 私有构造器,防止被new出来
 7      */
 8     private Singleton2 (){
 9     }
10     
11     /**
12      * 第一次使用时初始化
13      * @return
14      */
15     public static Singleton2 getInstance(){
16         if (sin == null){
17             sin = new Singleton2();
18         }
19         return sin;
20     }
21 }

  上面的单例模式基本可以满足程序,但是如果处于多线程环境下肯定会出现问题,所以在上面代码的基础上进行加锁。

 1 public class Singleton3 {
 2     private static Singleton3 sin = null;
 3 
 4     private Singleton3 (){
 5     }
 6     
 7     /**
 8      * 在初始化方法上面进行加锁操作
 9      * @return
10      */
11     public static synchronized Singleton3 getInstance(){
12         if (sin == null){
13             sin = new Singleton3();
14         }
15         return sin;
16     }
17 }

  上面代码对初始化方法进行加锁,但是是对整个方法进行了加锁,粒度有点大,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。

 1 public class Singleton4 {
 2     private static Singleton4 sin = null;
 3 
 4     private Singleton4 (){
 5     }
 6     
 7     /**
 8      * 只在new时进行加锁,双重同步锁
 9      * @return
10      */
11     public static Singleton4 getInstance(){
12         if (sin == null){
13             synchronized (sin){
14                 if (sin == null){
15                     sin = new Singleton4();
16                 }
17             }
18         }
19         return sin;
20     }
21 }

  上述代码看似没问题,其实还是会存在一些问题,当A线程执行sin = new Singleton4()时,jvm会先划分一部分空白内存并赋值给sin,然后走出synchronized方法块,然后B线程执行时发现sin已经有引用就直接返回,其实此时sin还没有被实例化。所以就有了下面的方式,既不用加锁,也可以实现懒加载。

 1 public class Singleton5 {
 2     private Singleton5 (){
 3     }
 4     /**
 5      * 使用内部类来维护单例。
 6      * @author Wuyouxin
 7      *
 8      */
 9     private static class SingletonFactory{
10         private static Singleton5 sin = new Singleton5();
11     }
12     
13     /**
14      * 当第一次调用时加载上面的内部类,初始化sin
15      * @return
16      */
17     public static Singleton5 getInstance(){
18         return SingletonFactory.sin;
19     } 
20     
21 }

  上述几种方式都可以实现单例模式,可以在不同的业务场景使用不同的方式去创建单例模式。

原文地址:https://www.cnblogs.com/wuyx/p/8850026.html