单例模式是一种常用的软件设计模式,其定义是该类只允许一个实例存在,所有使用的对都是同一个对象,当然Java中使用反射可以打破封装,即使在构造器私有化时也能够通过反射创建该类实例,当然我们这里并不考虑,否则单例将无法实现
在很多应用场景,我们只需要一个对象即可,比如线程池、缓存、工具类对象、连接池对象、日志对象等,如果出现多个实例,那么程序将可能出现不可预知的错误,单实例不仅能够保证程序使用的准确性,同样也避免频繁创建和销毁对象所带来的开销
实现步骤
主要有两种实现模式
下面来举例说明几种实现单例的方式
1)线程不安全的懒汉 - 多线程环境可能创建多个实例
package com.huawei.design.singleton;
/**
* @author: [email protected]
* @date: 2019/5/4 14:33
* @description: 线程不安全懒汉模式实现
*/
public class UnsafeLazySingleton {
private static UnsafeLazySingleton unsafeLazySingleton;
//私有化构造器
private UnsafeLazySingleton(){}
//提供访问点
public static UnsafeLazySingleton getInstance(){
if (unsafeLazySingleton == null){
unsafeLazySingleton = new UnsafeLazySingleton();
}
return unsafeLazySingleton;
}
}
优点:达到了延迟实例化实例的效果,避免了资源浪费。
缺点:在单线程下可用,多线程环境下可能创建多个实例,导致单例遭到破坏。
在单线程环境可以使用,在多线程环境不推荐,应该使用下面线程安全的实现方式。
推荐指数-★
2)线程安全的懒汉-使用静态同步方法获取实例
package com.huawei.design.singleton;
/**
* @author: [email protected]
* @date: 2019/5/4 14:43
* @description: 线程安全懒汉模式实现
*/
public class SafeLazySingleton {
private static SafeLazySingleton safeLazySingleton;
private SafeLazySingleton(){}
public static synchronized SafeLazySingleton getInstance(){
if (safeLazySingleton == null){
safeLazySingleton = new SafeLazySingleton();
}
return safeLazySingleton;
}
}
优点:既实现了延迟实例化实例的效果,也同时保证了线程安全。
缺点:如果在多线程环境下使用,每次获取实例都需要获取锁,竞争锁开销较大。
虽然进行了线程安全同步,但性能开销比较大。
推荐指数-★★
3)线程不安全的懒汉-使用锁代码方式
package com.huawei.design.singleton;
/**
* @author: [email protected]
* @date: 2019/5/4 14:33
* @description: 线程不安全懒汉模式实现
*/
public class UnsafeLazySingleton {
private static UnsafeLazySingleton unsafeLazySingleton;
//私有化构造器
private UnsafeLazySingleton(){}
//提供访问点
public static UnsafeLazySingleton getInstance(){
if (unsafeLazySingleton == null){
synchronized (UnsafeLazySingleton.class){
unsafeLazySingleton = new UnsafeLazySingleton();
}
}
return unsafeLazySingleton;
}
}
优点:达到了延迟实例化实例的效果。
缺点:本质上不是线程安全的,为什么说是线程不安全的,当两个线程同时执行到synchronized代码块时,一个线程将阻塞等待获取锁,一个线程将获取锁,获取到锁的线程执行代码块内容,初始化实例,当执行完代码块内容,释放锁,返回创建的实例,这时候等待锁的线程获取锁,又会在执行一遍代码块的内容,重新初始化一个实例,将导致单例的破坏,这也就是下面Double Check的由来。
由于存在线程安全问题,不推荐使用。
推荐指数-★
4)线程安全的懒汉-双重检查(Double Check)
package com.huawei.design.singleton;
/**
* @author: [email protected]
* @date: 2019/5/4 14:43
* @description: 线程安全懒汉模式实现
*/
public class SafeLazySingleton {
//这里使用volatile关键字很重要
private static volatile SafeLazySingleton safeLazySingleton;
private SafeLazySingleton(){}
public static SafeLazySingleton getInstance(){
if (safeLazySingleton == null){
synchronized (SafeLazySingleton.class){
if (safeLazySingleton == null){
safeLazySingleton = new SafeLazySingleton();
}
}
}
return safeLazySingleton;
}
}
优点:使用了双重检查,避免了线程不安全,同时也避免了不必要的锁开销。
缺点:暂无。
这里为什么了需要使用volatile关键字来修饰实例变量,因为在执行 safeLazySingleton = new SafeLazySingleton();这行代码时,实际上分为如下3行伪代码执行
memory = allocate();//1:分配对象所需的内存空间
ctorInstance(memory);//2:初始化对象
safeLazySingleton = memory;//3:将safeLazySingleton 指向该内存地址
上面2和3之间,可能会在执行的时候重排序(JIT编译优化或者CPU乱序执行),也就是Out-of-order-writes,重排序之后那么就先将safeLazySingleton 指向该内存地址,然后进行初始化,但是如果在执行初始化之前,有另外一个线程访问获取实例,这时候safeLazySingleton 不为null但还未初始化,那么将返回一个尚未初始化的实例给这个线程,而这个线程在使用未初始化的实例出现未知的错误,使用volatile修改变量将可以达到紧致指令重排序的效果,从未避免上述情况的发生,详细原理可参见《双重检查锁定与延迟初始化》。
既满足了延迟初始化的效果,并且兼具性能,同时也是线程安全的。
推荐指数-★★★★
5)线程安全的饿汉-静态变量初始化
package com.huawei.design.singleton;
/**
* @author: [email protected]
* @date: 2019/5/4 14:33
* @description: 线程安全饿汉模式实现
*/
public class SafeEagerSingleton {
private static SafeEagerSingleton safeEagerSingleton = new SafeEagerSingleton();
//私有化构造器
private SafeEagerSingleton(){}
//提供访问点
public static SafeEagerSingleton getInstance(){
return safeEagerSingleton;
}
}
优点:实现简单,无线程同步问题。
缺点:在加载类时完成实例初始化。可能会造成资源浪费。
相比双重检查实现方式更为简单,且性能好,无线程安全问题,在必定需要使用这个实例的场景可以使用。
推荐指数-★★
6)线程安全的饿汉-静态代码块初始化
package com.huawei.design.singleton;
/**
* @author: [email protected]
* @date: 2019/5/4 14:33
* @description: 线程安全饿汉模式实现
*/
public class SafeEagerSingleton {
private static SafeEagerSingleton safeEagerSingleton;
static {
safeEagerSingleton = new SafeEagerSingleton();
}
//私有化构造器
private SafeEagerSingleton(){}
//提供访问点
public static SafeEagerSingleton getInstance(){
return safeEagerSingleton;
}
}
优点:同上
缺点:同上
推荐指数-★★
7)线程安全的懒汉-静态内部类
package com.huawei.design.singleton;
/**
* @author: [email protected]
* @date: 2019/5/4 14:33
* @description: 线程安全懒汉模式实现
*/
public class SafeLazySingleton {
private static SafeLazySingleton safeLazySingleton;
//私有化构造器
private SafeLazySingleton(){}
//提供访问点
public static SafeLazySingleton getInstance(){
return InnerClass.INSTANCE;
}
private static class InnerClass {
private static final SafeLazySingleton INSTANCE = new SafeLazySingleton();
}
}
优点:无线程同步问题,实现了延迟初始化效果,只有调用getInstance时才会装载内部类,才会创建实例
缺点:暂无
在实现延迟初始化的同时且线程安全,避免加锁,使用内部类时,先调用内部类的线程会获得类初始化锁,从而保证内部类的初始化安全
推荐指数-★★★★★
8)枚举
package com.huawei.design.singleton;
/**
* @author: [email protected]
* @date: 2019/5/4 15:53
* @description:枚举实现单例
*/
public enum SafeEagerSingleton {
INSTANCE;
public void whateverMethod() {
}
}
优点:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
缺点:使用的是枚举,而非类。
在《Effective Java》书中第三条有说明使用枚举来实现单例的最佳方法
推荐指数-★★★★★
以上主要介绍了实现单例的8中方式,之所以有这么多种方式,来源于ava的特性发展以及各种场景的需要,具体使用还需根据实际的应用场景进行选择,没有最好的单例,只有最合适的单例
(转载请注明出处)