单例模式

单例模式

简介

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

一、饿汉式

单例对象已经创建好了,直接取就完事了

/**
 * 单例---饿汉模式
 */
class Res{
    private Res(){}
    private static final Res res = new Res();
    
    public static Res getInstance(){
        return res;
    }
}

二、懒汉式

需要单例对象的时候才去创建

单线程下安全,多线程不安全

class Res2{
    private Res2(){}
    private  static Res2 res;

    public static Res2 getInstance(){
        if(null == res){
            res = new Res2();
        }
        return res;
    }
}

多线程下

  1. 直接使用同步锁
  2. 双重校验锁
  3. 双重校验锁【volatile关键字
1. 直接使用同步锁

存在的问题:这样写的话,每个线程来都要竞争锁,可能造成大量阻塞

class Res2{
    private Res2(){}
    private  static Res2 res;

    public synchronized static Res2 getInstance(){
        if(null == res){
            res = new Res2();
        }
        return res;
    }
}
2. 双重校验锁

存在的问题:因为res = new Res2()不是原子操作,需要

  1. 分配内存
  2. 初始化对象
  3. 将引用指向对象地址

JVM可能会进行指令重排,比如 1->3->2。即:分配内存后,直接将引用res指向内存地址

这时候其他线程来判断 res!=null,就直接把未初始化对象的res给拿去用了。造成错误

class Res2{
    private Res2(){}
    private  static Res2 res;

    public static Res2 getInstance(){
        if(null == res){
             synchronized (Res2.class){
                if(null == res){
                    // 不是一个个原子操作---可能出现指令重排
                    // 1.分配内存   2.初始化对象    2.将这个引用res指向这个对象
                    // 正常顺序:1-2-3     重排:1 3 2
                    // 多线程情况下,就存在其他线程拿到的是一个空对象
                    res = new Res2();
                }
            }
        }
        return res;
    }
}

如何解决?volatile关键字

作用:防止指令重排

class Res2{
    private Res2(){}
    // 使用volatile防止指令重排
    private volatile static Res2 res;
    public static Res2 getInstance(){
        if(null == res){
            // 多个线程进入,再判断是否持有锁
            synchronized (Res2.class){
                if(null == res){
                    res = new Res2();
                }
            }
        }
        return res;
    }
}

三、静态内部类

使用静态内部类创建单例对象

线程安全,类加载过程由类加载器负责加锁,从而保证线程安全。并且类只加载一次

/**
 * 静态内部类实现单例
 * 多线程:安全
 */
class Res4{

    private Res4(){}
    private static class Res5{
        private static Res4 res = new Res4();
    }

    public static Res4 getInstance(){
        return Res5.res;
    }
}

四、枚举创建单例

上面3种创建单例的方法都可以被反射破坏,但不能使用反射创建枚举对象

报:Cannot reflectively create enum objects

/**
 * 枚举实现单例:
 *      说明:上面2种模式,都可以通过暴力反射获取第二个对象。破坏单例模式
 *      但枚举类不可被反射破坏
 */
enum Res3{
    SUMMER;
    public static Res3 getInstance(){
        return Res3.SUMMER;
    }
    // 默认构造函数:(String,int)
}

反射创建测试:

public class Demo1 {

    public static void main(String[] args) throws Exception {
        Res3 res1 = Res3.getInstance();
        Res3 res2 = Res3.getInstance();

        System.out.println(res1+":"+res2);

        Constructor<? extends Res3> constructor = res1.getClass().getDeclaredConstructor(String.class,int.class);
        // 取消安全检查
        constructor.setAccessible(true);
        // 报:Cannot reflectively create enum objects
        // 无法反射创建枚举对象
        Res3 res3 = constructor.newInstance();

    }

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值