单例模式的5种实现方式

单例模式:简单来说一个类只能构建一个对象

特征:

1、私有构造函数

2、静态方法返回单例类的对象

3、保证单例类对象只有一个,要注意多线程场景

4、如果单例对象在反序列化时,不会重新创建对象

1、懒汉式

public class Singleton {
    private Singleton() {}  //私有构造函数
    private static Singleton instance = null;  //单例对象
    //静态工厂方法
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

为什么这样写呢?我们来解释几个关键点:

1、要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此Signleton的构造方法是私有的。

2、instance是Singleton类的静态成员,也是我们的单例对象。它的初始值可以写成Null,也可以写成new Singleton()。至于其中的区别后来会做解释。

3、getInstance是获取单例对象的方法。

如果单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式。

如果单例对象一开始就被new Singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式。

这两个名字很形象:饿汉主动找食物吃,懒汉躺在地上等着人喂。

实现一个线程安全的单例模式

假设Singleton类刚刚被初始化,instance对象还是空,这时候两个线程同时访问getInstance()方法

因为Instance是空,所以两个线程同时通过了条件判断,开始执行new操作:

这样一来,显然instance被构建了两次。让我们对代码做一下修改:

双重检测机制。

public class Singleton {
    private Singleton() {}  //私有构造函数
    private static Singleton instance = null;  //单例对象
    //静态工厂方法
    public static Singleton getInstance() {
        if (instance == null) {      //双重检测机制
            synchronized (Singleton.class){  //同步锁
                if (instance == null) {     //双重检测机制
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 为什么这样写呢?我们来解释几个关键点:

1、为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。

2、进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。

双重检测失效问题:

new Singleton()时一般 来说有三个步骤

1、分配一块内存

2、在内存上初始化Singleton对象

3、把这块内存地址返回赋值给instance

但经过编译器的优化,2和3的顺序有可能颠倒的,也就是说可能你拿到的instance可能还没有被初始化,访问instance的成员变量就没有发生空指针异常,而volatile可以阻止这种情况的发生。

防止指令重排:

public class Singleton {
    private Singleton() {}  //私有构造函数
    private static volatile Singleton instance = null;  //单例对象
    //静态工厂方法
    public static Singleton getInstance() {
        if (instance == null) {      //双重检测机制
            synchronized (Singleton.class){  //同步锁
                if (instance == null) {     //双重检测机制
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2、饿汉式

线程安全的单例

class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {
    }
    
    public static Singleton getInstance() {
        return instance;
    }
}

3、静态内部类单例

public class Singleton {
    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    /**
     * 静态内部类
     */
    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }
}

当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getInstance()方法从而触发

SingletonHolder.instance 时 SingletonHolder 才会被加载,此时初始化 instance实例,并且 JVM 能确保 instance只被实例化一次。

做到了延迟加载和线程安全

优点:

1. 不用synchronized ,节省时间。

2. 调用getInstance() 的时候才会创建对象,不调用不创建,节省空间,这有点像懒汉式。

4、枚举单例

public enum SingleTon{
  INSTANCE;
        public void method(){
        //TODO
     }
}

枚举在java中与普通类一样,都能拥有字段与方法,而且枚举实例创建是线程安全的,在任何情况下,它都是一个单例。我们可直接以

5、容器实现单例

import java.util.HashMap;
import java.util.Map;
/**
 * 容器类实现单例模式
 */
public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();

    public static void regsiterService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return objMap.get(key);
    }
}

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>