单例模式详解
前言:单例模式在众多设计模式中是最基础的,也是最容易理解的;但是基础归基础,并不说明它不重要,Spring容器对象创建和依赖注入都用到了单例模式。
一、什么是单例模式?
定义:顾名思义,单例模式就是确保类只有一个实例,而且自行实例化并向整个系统提供这个实例。
特点:
- 构造方法私有化;
- 实例化的变量引用私有化;
- 获取实例的方法共有。
注意:
- 没有开放发构造方法,只对外提供一个能获取到该类实例的方法,并且保证任何调用者获取到的都是同一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例对象应做到多线程共享。(这就意味着为了避免线程安全问题,单例对象应该是无状态。)
二、实现单例模式的八种方式
首先,单例模式在广泛意义上可以被分为两类:饿汉式和懒汉式;它们的区别就在于实例化时机不同,饿汉式的实例化是与类加载在同一个阶段,而懒汉式的实例化是被调用时才进行,换句话说,若程序中没有用到该实例的话,它将永远不会实例化。
饿汉式:
1、通过<静态常量>实现;【可应用】
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
// 单例模式的所有构造方法必须为被private修饰
private Singleton() {
}
// 对外提供一个获取实例的方法
public static Singleton getInstance(){
return INSTANCE;
}
}
优点:写法比较简单,在类加载时就完成了创建实例的操作,避免了线程同步问题。
缺点:这种方式没有达到懒加载的效果(Lazy Loading),实例一直存在,无论是否被用到都占用着内存空间。
2、通过<静态代码块>实现;【可应用】
public class Singleton {
private static Singleton instance;
static {
// 此处可以加上实例的创建细节。
instance = new Singleton();
}
// 单例模式的所有构造方法必须为被private修饰
private Singleton() {
}
// 对外提供一个获取实例的方法
public static Singleton getInstance(){
return instance;
}
}
优点:相比于前一种,可以在静态代码块中追加一些自定义的细节;创建时机同样是在类加载时期完成。
缺点:与静态常量方式的缺点是一致的。
懒汉式:
3、线程不安全情况;【反例:不可用】
public class Singleton {
private static Singleton instance;
// 单例模式的所有构造方法必须为被private修饰
private Singleton() {
}
// 对外提供一个获取实例的方法
public static Singleton getInstance(){
if (instance == null){
// 在实例为创建时先创建实例再返回,看似逻辑清晰但是存在线程安全问题,仅仅适用于单线程的情况。
instance = new Singleton();
}
return instance;
}
}
适用于单线程情况;在多线程环境下,如果同时两个线程都满足if条件并进入if语句,在容器中便会产生连个实例。
4、优化第3种,通过<同步方法>实现;【可用,但不推荐】
public class Singleton {
private static Singleton instance;
// 单例模式的所有构造方法必须为被private修饰
private Singleton() {
}
// 对外提供一个获取实例的方法
public static synchronized Singleton getInstance(){
if (instance == null){。
instance = new Singleton();
}
return instance;
}
}
与第3种的区别就是在getInstance方法上加上了synchronized修饰;
优点:解决了线程安全问题;
缺点:因为每次获取实例时都要先获取锁,这样大大的降低了性能。
5、通过<同步代码块>实现,还是存在线程安全问题;【反例:不可用】
public class Singleton {
private static Singleton instance;
// 单例模式的所有构造方法必须为被private修饰
private Singleton() {
}
// 对外提供一个获取实例的方法
public static Singleton getInstance(){
if (instance == null){
// 加同步代码块的本意是,仅仅在第一次访问时需要获取锁,之后再也不用获取锁。(但是还是存在问题)
synchronized (Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
此方法看似逻辑无误,但是还是存在线程安全问题;原因与第3种方式一致。
6、优化第5种,通过双重检测锁实现;【推荐使用】
public class Singleton {
private static volatile Singleton instance;
// 单例模式的所有构造方法必须为被private修饰
private Singleton() {
}
// 对外提供一个获取实例的方法
public static Singleton getInstance(){
//第一次检测
if (instance == null){
synchronized (Singleton.class){
// 第二次检测
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
通过上边应用的双重检测锁,可以避免创建多个实例。
7、通过<静态内部类>实现;【推荐使用】
public class Singleton {
// 单例模式的所有构造方法必须为被private修饰
private Singleton() {
}
// 对外提供一个获取实例的方法
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
public static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
}
这种方式是利用了,类加载时的同步特性和静态内部类的延迟加载特性实现的;这种方式实例的创建也是在第一此调用时,即第一次调用时才会加载静态内部类。
8、通过<枚举>实现;【推荐使用】
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
完整的枚举单例;
public class User {
//私有化构造函数
private User(){ }
//定义一个静态枚举类
static enum SingletonEnum{
//创建一个枚举对象,该对象天生为单例
INSTANCE;
private User user;
//私有化枚举的构造函数
private SingletonEnum(){
user=new User();
}
public User getInstnce(){
return user;
}
}
//对外暴露一个获取User对象的静态方法
public static User getInstance(){
return SingletonEnum.INSTANCE.getInstnce();
}
}
public class Test {
public static void main(String [] args){
System.out.println(User.getInstance());
System.out.println(User.getInstance());
System.out.println(User.getInstance()==User.getInstance());
}
}
结果为true
以上就是单例模式的8中实现,其中枚举的单例实现被称为最完美的单例实现,他能解决,反序列化和反射带来的多实例问题。