设计模式——单例模式

饿汉加载实现

    public class SingleTon{

        private static SingleTon INSTANCE = new SingleTon();
        private SingleTon(){
            System.out.println("==初始化成功==");
        }
        public static SingleTon getInstance(){ return INSTANCE; }
    }

1.声明一个private静态变量INSTANCE ,并赋值
2.提供一个公共方法返回该实例

饿汉模式声明的是赋予值的静态变量,所以在类加载的时候就完成初始化:类加载3阶段(加载 》链接》 初始化)在第三阶段初始化阶段完成赋值

懒汉模式实现(分为线程安全和线程不安全)

线程不安全的:(1.没有上锁 2.没有加volatile变量防止指令重排序)

//懒汉式单例模式
public class MySingleton {

    //设立静态变量
    private static MySingleton mySingleton = null;

    private MySingleton(){
        //私有化构造函数
        System.out.println("-->懒汉式单例模式开始调用构造函数");
    }

    //开放一个公有方法,判断是否已经存在实例,有返回,没有新建一个在返回
    public static MySingleton getInstance(){
        System.out.println("-->懒汉式单例模式开始调用公有方法返回实例");
        if(mySingleton == null){
            System.out.println("-->懒汉式构造函数的实例当前并没有被创建");
            mySingleton = new MySingleton();
        }else{
            System.out.println("-->懒汉式构造函数的实例已经被创建");
        }
        System.out.println("-->方法调用结束,返回单例");
        return mySingleton;
    }
}

线程安全:

public class MySafeSingleTon {
    //设立静态变量
    //volatile防止指令重排 保证内存可见
    private static volatile MySafeSingleTon mySingleton = null;

    private MySafeSingleTon(){
        //私有化构造函数
        System.out.println("-->懒汉式单例模式开始调用构造函数");
    }

    //开放一个公有方法,判断是否已经存在实例,有返回,没有新建一个在返回
    public static MySafeSingleTon getInstance(){
        System.out.println("-->懒汉式单例模式开始调用公有方法返回实例");
        if(mySingleton == null){
            System.out.println("-->懒汉式构造函数的实例当前并没有被创建");
            synchronized (MySafeSingleTon.class){
                if (mySingleton==null){
                    mySingleton = new MySafeSingleTon();
                }
            }
        }
        return mySingleton;
    }
}

饿汉模式和懒汉模式的区别

饿汉模式:
*是否 Lazy 初始化:否
*是否多线程安全:是
*实现难度:易
*描述:这种方式比较常用,但容易产生垃圾对象。
*优点:没有加锁,执行效率会提高。
*缺点:类加载时就初始化,浪费内存。
*它基于 classloder 机制避免了多线程的同步问题,
* 不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,
* 在单例模式中大多数都是调用 getInstance 方法,
* 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,
* 这时候初始化 instance 显然没有达到 lazy loading 的效果

懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

摘抄自:https://blog.youkuaiyun.com/qq_35098526/article/details/79893628

自己的理解:从jvm层面,饿汉模式在类加载阶段(第三阶段初始化阶段)就完成了给instance 的赋值,而懒汉模式只有在调用getInstance的时候才会赋值,类加载阶段给mySingleton赋值为null

静态内部类实现

public class MySingleToninternal{

    private MySingleToninternal(){}

    private static class SingleTonHoler{
        private static MySingleToninternal INSTANCE = new MySingleToninternal();
    }

    public static MySingleToninternal getInstance(){
        return SingleTonHoler.INSTANCE;
    }
}

调用:

public class test {
    public static void main(String[] args) {
        MySingleToninternal.getInstance();
    }
}

实现原理:jvm内部保证线程安全

枚举实现

public enum DataSourceEnum {
    DATASOURCE;
    private DBConnection connection = null;
    private DataSourceEnum() {
        connection = new DBConnection();
    }
    public DBConnection getConnection() {
        return connection;
    }
}  
 public class Main {
    public static void main(String[] args) {
        DBConnection con1 = DataSourceEnum.DATASOURCE.getConnection();
        DBConnection con2 = DataSourceEnum.DATASOURCE.getConnection();
        System.out.println(con1 == con2);
    }
}

就拿枚举来说,其实Enum就是一个普通的类,它继承自java.lang.Enum类。

public enum DataSourceEnum {
DATASOURCE;
}

把上面枚举编译后的字节码反编译,得到的代码如下:

public final class DataSourceEnum extends Enum<DataSourceEnum> {
      public static final DataSourceEnum DATASOURCE;
      public static DataSourceEnum[] values();
      public static DataSourceEnum valueOf(String s);
      static {};
}

由反编译后的代码可知,DATASOURCE 被声明为 static 的,根据在【单例深思】饿汉式与类加载 中所描述的类加载过程,可以知道虚拟机会保证一个类的() 方法在多线程环境中被正确的加锁、同步。所以,枚举实现是在实例化时是线程安全。

接下来看看序列化问题:

Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf() 方法来根据名字查找枚举对象。
也就是说,以下面枚举为例,序列化的时候只将 DATASOURCE 这个名称输出,反序列化的时候再通过这个名称,查找对于的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。

public enum DataSourceEnum {
DATASOURCE;
}

由此可知,枚举天生保证序列化单例。 【枚举摘抄自:枚举实现单例

破坏单例模式的方法及解决办法

1、除枚举方式外, 其他方法都会通过反射的方式破坏单例,反射是通过调用构造方法生成新的对象,所以如果我们想要阻止单例破坏,可以在构造方法中进行判断,若已有实例, 则阻止生成新的实例,解决办法如下:

private SingletonObject1(){
    if (instance !=null){
        throw new RuntimeException("实例已经存在,请通过 getInstance()方法获取");
    }
}

2、如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例,所以我们可以不实现序列化接口,如果非得实现序列化接口,可以重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象。

  public Object readResolve() throws ObjectStreamException {
        return instance;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值