单例模式在java中十分常用, 通常单例模式包括:
- 懒汉式单例模式
- 饿汉式单例模式
- 登记式单例模式(这种忽略)
1. 单例只能有一个实例对象, 2. 单例类必须自己创建自己的唯一实例, 所以构造函数私有化 3. 单例类必须给所有其他对象提供这一实例。 所以要提供一个公共的方法工外部获取这个类的实例。 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
二、懒汉式单例模式由于它的对象实在外部调用公共方法时获取该对象时才进行实例化的, 所以成为懒汉式单例模式。 懒汉式单例模式 : (1)构造函数私有化 (2)声明一个私有的对象 (3)提供一个公共的方法供外部获取实例化对象 但是懒汉式单例模式,是线程不安全的。
package com.chb.singlePattern;
/**
懒汉式单例模式 :
构造函数私有化
声明一个私有的对象
提供一个公共的方法供外部获取实例化对象
*/
public class LazySingleton {
private LazySingleton() {}
private static LazySingleton singleton = null;
/**
* 在多线程情况下, 多个线程同时访问这个方法,存在线程安全问题。
* 这是懒汉式单例模式存在的隐患,需要改进。
* @return
*/
public static LazySingleton getInstance() {
if (singleton == null) {//如果有两个线程同时进入该方法,会创建两个对象。
singleton = new LazySingleton();
}
return singleton;
}
}
LazySingleton 将通过将构造函数限定为private,避免了该类在外部被实例化,在同一个虚拟机范围内,LazySingleton 的唯一实例只能通过getInstance()这个方法获取(不考虑反射机制)。
三、懒汉单例模式的改进因为懒汉模式在多线程的情况下,是不安全的, 这个隐患需要解决,下面我们可以通过三种方式
3.1、在getInstance()方法加上syncronized ,进行同步/**
懒汉式单例模式的改进方法一:
在getInstance()方法加上syncronized ,进行同步
*/
class LazySingletonSync {
private LazySingletonSync() {}
private static LazySingletonSync lazySingletonSync = null;
public static synchronized LazySingletonSync getInstance() {
if (lazySingletonSync == null) {
lazySingletonSync = new LazySingletonSync();
}
return lazySingletonSync;
}
}
3.2、双重检查锁定
double-check 在 J2SE 1.4 或早期版本在多线程或者 JVM 调优时由于 out-of-order writes,是不可用的。 这个问题在 J2SE 5.0 中已经被修复,可以使用 volatile 关键字来保证多线程下的单例。 双重检查锁定不是线程安全的,如果要用这种方式,需要使用volatile关键字。
假设没有关键字volatile的情况下,两个线程A、B,都是第一次调用该单例方法,线程A先执行instance = new Instance(),该构造方法是一个非原子操作,编译后生成多条字节码指令,由于JAVA的指令重排序,可能会先执行instance的赋值操作,该操作实际只是在内存中开辟一片存储对象的区域后直接返回内存的引用,之后instance便不为空了,但是实际的初始化操作却还没有执行,如果就在此时线程B进入,就会看到一个不为空的但是不完整(没有完成初始化)的Instance对象,所以需要加入volatile关键字,禁止指令重排序优化,从而安全的实现单例。
/**
懒汉式单例模式的改进方法二:
双重检查锁定
*/
class LazySingletonDoubleSync {
private LazySingletonDoubleSync() {}
// 双重检查锁定不是线程安全的,如果要用这种方式,需要使用volatile关键字。
private static volatile LazySingletonDoubleSync lazySingletonDoubleSync = null;
public static synchronized LazySingletonDoubleSync getInstance() {
if (lazySingletonDoubleSync == null) {
synchronized (LazySingletonDoubleSync.class) {
if (lazySingletonDoubleSync == null) {
lazySingletonDoubleSync = new LazySingletonDoubleSync();
}
}
}
return lazySingletonDoubleSync;
}
}
3.3、静态内部类
这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。
/**
懒汉式单例模式的改进方法三:
静态内部类:
*/
class LazySingletonInner {
//构造函数私有化
private LazySingletonInner() {}
//公共方法供外部获取实例化对象。
public static LazySingletonInner getInstance() {
return LazyHolder.INSTANCE;
}
/**
* 静态内部类: 提供一个static final 的实例化对象
* 保证了对象在类创建的时候就存在, 同时保证了对象的安全和唯一性
* 由于不用同步,所以效率较方法一、二更高
*/
private static class LazyHolder {
private static final LazySingletonInner INSTANCE = new LazySingletonInner();
}
}
四、饿汉式单例模式
由于该类在累的创建的同时,进行对象的实例化, 并且使用static final修饰, 保证实例化对象不可更改,保证了对象的唯一性。同时线程安全。 饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
package com.chb.singlePattern;
public class HungrySingleton {
//构造函数私有化
private HungrySingleton(){}
//提供一个静态的对象, 使用static final 修饰, 该对象不会被修改, 且在类创建同时存在。保证了对象的唯一性和安全
private static final HungrySingleton hungrySingleton = new HungrySingleton();
/** 提供一个公共方法供外部获取实例对象 */
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
五、三种懒汉模式的区别
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的。
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,但是不是线程安全的,如果要用这种方式,需要使用volatile关键字。可以避免了每次都同步的性能损耗。
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。