单例模式是指对一个对象进行一次实例化,然后全局都可以调用该实例化对象来完成项目的开发。
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
实现单例模式的方式
-
饿汉式
饿汉式单例是指在方法调用前,实例就已经创建好了。下面是实现代码://饿汉式 public class SingletonE { //私有化构造方法,在外部不能实例化对象 private SingletonE() {} /* * 实例化静态对象 * 优点:不存在线程安全问题。 * 缺点:系统加载时消耗额外资源,如果该实例没有使用的情况会造成资源浪费。 */ private static SingletonE instance = new SingletonE(); //静态类方法 public static SingletonE getInstance() { return instance; } }
-
懒汉式
懒汉式单例是指在方法调用获取实例时才创建实例,因为相对饿汉式显得“不急迫”,所以被叫做“懒汉模式”。下面是实现代码://懒汉式 public class SingletonLan { //私有化构造方法,在外部不能实例化对象 private SingletonLan() {} //申明静态对象 private static SingletonLan instance; //静态类方法 public static SingletonLan getInstance() { //以下代码不是原子性操作,会出现线程安全问题。 if (instance == null) { instance = new SingletonLan(); } return instance; } }
在以上代码中,在if语句里面,就可能跑有多个线程同步判断和同步new。会产生线程安全问题。
解决方案:
- 方案1:静态类方法加上线程锁(synchronized)
这样就线程安全了,但是在多个线程访问环境下,同步方法会导致系统性能下降。
不建议该方案//静态类方法 public synchronized static SingletonLan getInstance() { if (instance == null) { instance = new SingletonLan(); } return instance; }
- 方案2:双重检查锁定(Double-Check Locking)
使用延时加载技术,避免类加载时任务过重和造成资源浪费,同时将synchronized关键字加在代码块中,减少线程同步锁定以提升系统性能。instance实例使用了volatile关键字修饰,主要是避免在多线程环境下由于编译器进行的重排序操作而导致的线程安全问题。JVM在创建一个对象时会进行以下步骤:
1)分配对象内存空间;
2)初始化对象;
3)设置instance指向分配的内存地址;
编译器为了优化性能,可能会将2、3操作调换顺序,假设A线程在执行new Singleton()方法时,由于2、3操作重排序,而初始化对象操作尚未完成时释放了锁。线程B获取锁之后会发现instance已经不为空,当线程B获取到instance对象后如果直接使用就会出错,原因就是对象没有进行初始化操作。而volatile关键字能避免重排序,因此能保证线程安全。总体上来说,双重检测由于加了锁,多线程并发下还是会有效率问题。下面是实现代码://懒汉式--双重检查锁定 public class SingletonLan { //私有化构造方法,在外部不能实例化对象 private SingletonLan() {} /* * 申明静态对象 * 加volatile关键字,是为了避免在多线程环境下由于编译器进行的重排序操作而导致的线程安全问题 */ private static volatile SingletonLan instance; //给外部提供一个访问该静态对象的静态方法 public static SingletonLan getInstance() { //以下代码不是原子性操作,会出现线程安全问题。 if (instance == null) { synchronized (SingletonLan.class) { if (instance == null) { instance = new SingletonLan(); } } } return instance; } }
- 方案1:静态类方法加上线程锁(synchronized)
-
静态嵌套类单例
//静态内部类单例(Static Inner Class) public class SingletonSIC { //私有化构造方法,在外部不能实例化对象 private SingletonSIC() {} //静态类方法 public static SingletonSIC getInstance() { return SingletonSICFactory.INSTANCE_SIC; } //申明一个静态内部类 static class SingletonSICFactory{ private static final SingletonSIC INSTANCE_SIC = new SingletonSIC(); } }
静态内部类单例模式是一种比较优秀的实现方式,也是《Effective Java》书中推荐的方式。一方面,使用延时加载,使用时才进行对象初始化,也不会造成造成资源浪费;另一方面,由于JVM在类的加载时已经做了同步处理,不会出现线程安全问题。