单例模式的定义:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一的实例。
单例模式是一种对象创建型模式,主要是3个要点: (1)只能有一个实例;(2)必须是自行创建这个实例;(3)必须自行向整个系统提供这个实例。
单例模式代码实现:
package com.create.single; public class Singleton { //静态私有成员变量 private static Singleton singleton = null; //私用构造函数 private Singleton() { }
//公用静态成员方法,返回唯一实例 public static Singleton getSingleton() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
测试类:
package com.create.single; public class Client { public static void main(String[] args) { Singleton s1 = Singleton.getSingleton(); Singleton s2 = Singleton.getSingleton(); System.out.println(s1 == s2); } }
运行结果:true
饿汉式单例和懒汉式单例
1,饿汉式单例模式是实现最简单的单例类,由于在定义静态变量的时候实例化单例类,因此在类加载的时候单例对象就已经创建了。
代码如下:
package com.create.single; /** * 饿汉模式 */ public class HungrySingleton { //定义静态变量 private static final HungrySingleton singleton = new HungrySingleton(); //私有化构造函数 private HungrySingleton(){}; //返回实例方法 public static HungrySingleton getSingleton() { return singleton; } }
2,懒汉式单例
与饿汉式相同的是类的构造函数都是私用的,不同的是懒汉式单例类在第一次被引用时将自己实例化,在类加载时不会被实例化,而饿汉式时是在类加载的时候就已经实例化了。
懒汉式单例代码:
package com.create.single; public class LazySingleton { public static LazySingleton singleton = null; private LazySingleton(){}; //加锁解决并发问题 public synchronized static LazySingleton getSingleton() { if(singleton == null) { singleton = new LazySingleton(); } return singleton; } }
加入synchronized锁虽然解决了线程安全问题,但是每次调用getSingleton()方法每次都需要进行线程锁判断,在多线程高并发环境中将导致系统性能降低的问题。
下面我们来使用双重检查锁定来另外实现懒汉式单例:
package com.create.single; public class LazySingleton { public volatile static LazySingleton singleton = null; private LazySingleton(){}; public static LazySingleton getSingleton() { //第一重判断 if(singleton == null) { //锁定代码块 synchronized (LazySingleton.class) { //第二重判断 if(singleton == null) { singleton = new LazySingleton(); } } } return singleton; } }
第二种实现虽然也加了锁,但是实现效率比第一种要好。但是由于volatile关键字会屏蔽JVM所做的一些代码优化,可能会导致系统的运行效率降低。所以这也不是最完美的实现方式。
饿汉式单例和懒汉式单例的比较:
饿汉式单例类在类加载的时候就将自己实例化了,优点是无需考虑多个线程并发问题,可以确保实例的唯一性。缺点是由于系统加载时需要创建饿汉式单例对象(资源利用率比较低),加载时间可能会比较长。
懒汉式单例类是在第一次使用的时候创建,无需一直占用资源,实现了延迟加载。但是由于会有多并发问题存在,所以使用双重检查锁定等机制进行控制,这将导致性能会受到一定的影响。
下面我们通过静态内部类的方式实现单例模式
package com.create.single; /** * 通过静态内部类的方式实现单例 */ public class StaticSingleton { //私有化构造函数 private StaticSingleton() {}; //静态内部类 private static class HolderClass { private final static StaticSingleton singleton = new StaticSingleton(); } //提供对外调用接口 private static StaticSingleton getSingleton() { return HolderClass.singleton; } //测试 public static void main(String[] args) { StaticSingleton s1 = StaticSingleton.getSingleton(); StaticSingleton s2 = StaticSingleton.getSingleton(); System.out.println(s1 == s2); } }
运行结果为:true
这种方式在类加载时不会被实例化,在第一次调用时会加载HolderClass静态内部类,此时会初始化这个成员变量,由JVM来保证其线程的安全,确保只会被初始化一次,由于没有加锁操作,所以该性能不会受到其它影响
通过这种方式既能实现延迟加载,又能保证线程安全,不影响系统性能,是一种比较完美的实现方式。
单例模式的优缺点
优点: (1)提供了唯一实例的受控访问,因为单例类封装了它的唯一实例,所以它可以控制客户端怎样以及何时访问它。
(2)节约系统资源,对于一些频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
缺点: (1)单例类没有抽象层,因此扩展难度大。
(2)一定程度上违背了单一职责,将对象的创建和对象本身的功能耦合在一起。
(3)由于自动垃圾回收机制的存在,如果实例化对象长时间不被利用,系统会认为时垃圾对象,会自动销毁并回收 资源,下次利用时又重新实例化,这会导致共享的实例化对象状态的丢失。