单例模式(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 }
上述几种方式都可以实现单例模式,可以在不同的业务场景使用不同的方式去创建单例模式。