饿汉式单例模式与懒汉式单例模式
饿汉式(在类加载(变量初始化时)的时候就已经实例化了)
public class HungrySingleton {
//存储HungrySingleton对象引用的静态变量hSingleton
private static HungrySingleton hSingleton = new HungrySingleton();
//私有构造方法
private HungrySingleton(){}
//获取实例对象的静态方法getInstance()
public static HungrySingleton getInstance(){
return hSingleton;
}
}
懒汉式(第一次调用时实例化)
public class LazySingleton {
//存储HungrySingleton对象引用的静态变量hSingleton
private static HungrySingleton lSingleton;
//私有构造方法
private LazySingleton(){}
//获取实例对象的静态方法getInstance()
public static LazySingleton getInstance(){
//判断实例对象是否已经存在,如果不存在创建新的实例对象并将引用赋给hSingleton变量
if(lSingleton == null)
lSingleton = new HungrySingleton();
//返回存在的实例对象(因为有了判断语句,所以返回的只会是指向同一个实例对象的变量hSingleton)
return lSingleton;
}
}
区别
-
饿汉式与懒汉式在代码上的主要区别就是实例化对象方式一个是在静态变量上初始化,一个是在静态方法内初始化;饿汉式在类加载时即变量初始化时就已经创建了实例对象,而懒汉式是在第一次调用时创建实例对象,也就是说饿汉式无论用不用到单例对象都会实例化(如果一直没有用到会占用资源),而懒汉式如果一次也不用就不会实例化。
-
饿汉式是线程安全的(在类加载时创建实例,JVM保证在任何线程 访问静态lSingleton变量之前,实例被创建),懒汉式是线程不安全的(因为是在调用时被创建,可能有多个线程同时走到if判断语句),一般懒汉式会加线程锁
加锁的懒汉式
public class LazySingleton {
//存储对象引用的静态变量lSingletonLock
private static LazySingleton lSingletonLock;
//其他有用的实例变量
//私有构造方法
private LazySingleton(){}
//加了线程锁的静态方法
public static synchronized LazySingleton getLazySingleton(){
//判断实例对象是否已经存在,如果不存在创建新的实例对象并将引用赋给lSingletonLock变量
if(lSingletonLock == null)
lSingletonLock = new LazySingleton();
//返回存在的实例对象(因为有了判断语句,所以返回的只会是指向同一个实例对象的变量lSingletonLock)
return lSingletonLock;
}
//其他有用的方法
}
通过增加synchronized关键字我们迫使每个线程进入此方法之前,要先等待轮到它。也就是说没有两个线程可以同时进入该方法。
同步虽然可以解决线程安全问题 ,但更糟糕的是,只有第一次执行此方法才和同步扯上关系。换句话说,一旦设置了lSingeltonLock变量为单件的实例,就不再需要同步这个方法。第一步之后,同步完全是累赘。
饿汉式与加锁懒汉式优缺点
缺点:
- 饿汉式无论你需不需要都会创建实例对象,所以可能会造成空间浪费
- 加锁懒汉式只有第一次调用时才会和同步扯上关系,第一步之后同步完全是累赘,后续执行都会迫使每个线程进入此方法之前,要先等待轮到它
优点:
- 饿汉式在加载时就已经创建实例对象没有线程安全问题,执行效率高
- 加锁懒汉式在第一次调用时才会创建对象,不会有浪费空间的缺点
双重加锁懒汉式
public class SingletonLock {
//volatile关键字确保当Singleton变量被初始化为单件实例时,多个线程正确处理Singleton变量
private volatile static SingletonLock Singleton;
//私有构造方法
private SingletonLock(){}
//获取实例对象的静态方法
public static SingletonLock getSingleton(){
//检查实例,如果没有进入,同步区块;如果存在不再进入同步
if(Singleton == null){
//只有第一次才同步
synchronized (SingletonLock.class){
//进入区块后,再检查一次,如果依然是空的,创建一个实例
if(Singleton == null){
Singleton = new SingletonLock();
}
}
}
//返回存在的实例对象(因为有了判断语句,所以返回的只会是指向同一个实例对象的变量)
return Singleton;
}
}
双重检查加锁避免了单重加锁懒汉式的同步缺点问题,双重加锁会首先检查实例是否已经创建,如果没有,才同步,否则,不会进入线程同步,节省了性能开销,大大减少了负载。
注:双重检查加锁不适用1.4及更早版本的Java,因为更早版本允许不恰当同步。