浅析java设计模式之单例模式

单例模式

描述:
单例模式是系统中单例的类只有一个实例化对象,系统中的所有对该类的调用和访问都通过该对象来进行

常用场景:
当一个系统中需要注意实例的个数,减少实例的频繁创建和销毁造成的资源浪费。
比如系统中的字典类就可以用单例模式来实现

项目结构:
在这里插入图片描述
实现:
1.懒汉式(最简单最基本的实现方法)
这种是单例模式实现最简单的方式,但是在多线程下会有并发问题。

/**
 * @author Carl
 * @version 1.0
 * @date 2020/8/10 14:29
 * @description 实现方式--懒汉式:这种是单例模式实现最简单的方式,但是在多线程下会有并发问题
 **/
public class MySingleton1 {
    private MySingleton1(){
    }

    private static MySingleton1 mySingleton1;

    public static MySingleton1 getInstance(){
        if(mySingleton1 == null){
            mySingleton1 = new MySingleton1();
        }
        return mySingleton1;
    }

    public void doSomething(){
        System.out.println("get a singleton");
    }
}

2.懒汉式-加synchronize锁
这种实现模式线程安全,并且在真正用到它的地方才会进行实例化,但是由于锁的竞争,对性能方面有一定影响。

/**
 * @author Carl
 * @version 1.0
 * @date 2020/8/10 14:31
 * @description 实现方式--懒汉式--线程安全:这种实现模式线程安全,并且在真正用到它的地方才会进行实例化,但是由于锁的竞争,对性能方面有一定影响
 **/
public class MySingleton2 {
    private MySingleton2(){}

    private static MySingleton2  mySingleton2;

    public static synchronized MySingleton2 getInstance(){
        if(mySingleton2 == null){
            mySingleton2 = new MySingleton2();
        }
        return mySingleton2;
    }

    public void doSomething(){
        System.out.println("get a singleton");
    }
}

3.饿汉式
这种单例模式通过类的加载来实现,由于没有加锁,因此在效率上没有太大的影响,但是如果在项目运行过程中有其他的方式加载类,这时就没有达到lazy loading的效果,也就是在实际第一次要使用到时才创建。

/**
 * @author Carl
 * @version 1.0
 * @date 2020/8/10 14:44
 * @description 实现方式--饿汉式:这种单例模式通过类的加载来实现,由于没有加锁,因此在效率上没有太大的影响,但是如果在项目运行过程中有其他的方式加载类,这时就没有达到lazy loading的效果
 **/
public class MySingleton3 {
    private MySingleton3(){}

    private static MySingleton3 mySIngleton3 = new MySingleton3();

    public static MySingleton3 getInstance(){
        return mySIngleton3;
    }

    public void doSomething(){
        System.out.println("get a singleton");
    }
}

4.静态内部类实现(懒汉式)
该方法通过静态内部类的方式实现懒加载,类初始化的时候MySingletonHandler并没有加载,只有在调用getinstance方法的时候才会通过 MySingletonHandler进行MySingleton实例的加载。由于只在真正调用的时候通过加载创建一个实例,因此也是线程安全的。但是静态内部类无法接受外部传参,这也是这种实现方式最大的缺陷。

/**
 * @author Carl
 * @version 1.0
 * @date 2020/8/4 16:43
 * @description 实现方式--静态内部类:该方法通过静态内部类的方式实现懒加载,类初始化的时候MySingletonHandler并没有加载,只有在调用getinstance方法的时候才会通过
 *  *              MySingletonHandler进行MySingleton实例的加载。由于只在真正调用的时候通过加载创建一个实例,因此也是线程安全的。但是静态内部类无法接受外部传参,这
 *  *              也是这种实现方式最大的缺陷
 **/
public class MySingleton {
    private static class MySinletonHandler{
        private static MySingleton mySingleton = new MySingleton();
        public static MySingleton getMySingleton(){
            return mySingleton;
        }
    }
    private MySingleton(){

    }
    public static MySingleton getInstance(){
        return MySinletonHandler.getMySingleton();
    }

    public void doSomething(){
        System.out.println("get a singleton");
    }
}

5.双重校验锁
这种单例的实现方式是通过双重检验锁进行同步控制的,当两个线程都运行到if(mySingleton4 == null) 的时候,线程1获得允许进入到synchronized代码块中后再次判断mySingleton4==null不等于null的时候创建一个新的实例,并退出同步代码块,然后线程2进入同步代码快,由于mySingleton4不为空,线程2直接退出同步代码块。这种方式在理论上可以实现线程安全,但是jvm的内存模型结构中的无序写入会导致 创建对象 时一段时间内mySingleton4引入了一个并没有被完全初始化的实例,线程2调用getInstance()的时候由于mySingleton4引用了一个不为空的但是初始化不完整的实例,导致单例模式的实现失败。尽管在jdk1.5之后 java在内存模型上做了很大的改善,增加了volatile关键字控制顺序执行,但是该实现方式却显得有点繁琐了。并且双重检验锁的实现方式可以被反射暴力破解。目前企业中大部分单例模式都是用这种方法实现的,只要不去主动的使用反射创建实例,一般不会出现什么问题。

/**
 * @author Carl
 * @version 1.0
 * @date 2020/8/10 16:34
 * @description 实现方式--双重校验锁:这种单例的实现方式是通过双重检验锁进行同步控制的,当两个线程都运行到if(mySingleton4 == null) 的时候,线程1获得允许进入到synchronized代码块中
 *              后再次判断mySingleton4==null不等于null的时候创建一个新的实例,并退出同步代码块,然后线程2进入同步代码快,由于mySingleton4不为空,线程2直接退
 *              出同步代码块。这种方式在理论上可以实现线程安全,但是jvm的内存模型结构中的无序写入会导致 创建对象 时一段时间内mySingleton4引入了一个并没有被完全
 *              初始化的实例,线程2调用getInstance()的时候由于mySingleton4引用了一个不为空的但是初始化不完整的实例,导致单例模式的实现失败。尽管在jdk1.5之后
 *              java在内存模型上做了很大的改善,增加了volatile关键字控制顺序执行,但是该实现方式却显得有点繁琐了。并且双重检验锁的实现方式可以被反射暴力破解。目
 *              前企业中大部分单例模式都是用这种方法实现的,只要不去主动的使用反射创建实例,一般不会出现什么问题。
 **/
public class MySingleton4 {
    private static MySingleton4 mySingleton4;
    //jdk1.5之后的写法
    //private static volatile MySingleton4  mySingleton4;

    public static MySingleton4 getInstance(){
        if(mySingleton4 == null){
            synchronized (MySingleton4.class){
                if(mySingleton4 == null){
                    mySingleton4 = new MySingleton4();//创建对象
                }
            }
        }
        return mySingleton4;
    }

    public void doSomething(){
        System.out.println("get a singleton");
    }
}

6.枚举实现
最后一种实现单例的方式目前也是最受信赖的实现方式。通过枚举类反编译之后其实是一个final class,枚举类的构造器默认就是private的,枚举类序列化的时候只输出name,反序列化的时候通过valueOf()方法查找,因此并不是传统意义上的序列化,也就不存在序列化问题。但是这种枚举的实现方式也是饿汉式的,在类初始化的时候就已经给实例分配了内存。

/**
 * @author Carl
 * @version 1.0
 * @date 2020/8/10 16:48
 * @description 实现方式--枚举:最后一种实现单例的方式目前也是最受信赖的实现方式。通过枚举类反编译之后其实是一个final class,枚举类的构造器默认就是private的,枚举类序列化
 *              的时候只输出name,反序列化的时候通过valueOf()方法查找,因此并不是传统意义上的序列化,也就不存在序列化问题。但是这种枚举的实现方式也是饿汉式
 *              的,在类初始化的时候就已经给实例分配了内存。
 **/
public enum MySingleton5 {
    INSTANCE;
    public void doSomething(){
        System.out.println("get a singleton");
    }
}

7.测试

import java.lang.reflect.Constructor;

/**
 * @author Carl
 * @version 1.0
 * @date 2020/8/4 16:49
 * @description 六种单例模式测试
 **/
public class Domain {
    public static void main(String[] args) {
        //静态内部类
        MySingleton mySingleton = MySingleton.getInstance();
        mySingleton.doSomething();
        //懒汉式,线程不安全
        MySingleton1 mySingleton1 = MySingleton1.getInstance();
        mySingleton1.doSomething();
        //懒汉式,线程安全
        MySingleton2 mySingleton2 = MySingleton2.getInstance();
        mySingleton2.doSomething();
        //饿汉式
        MySingleton3 mySingleton3 = MySingleton3.getInstance();
        mySingleton3.doSomething();
        //双重校验锁
        MySingleton4 mySingleton4 = MySingleton4.getInstance();
        mySingleton4.doSomething();
        //枚举
        MySingleton5.INSTANCE.doSomething();


        //使用反射暴力破解双重检验锁实现的单例模式
        try {
            Class<MySingleton4> clazz = (Class<MySingleton4>)Class.forName("com.yzj.dobetter.designPattern.singletonPattern.MySingleton4");
            Constructor<MySingleton4> c = clazz.getDeclaredConstructor();
            c.setAccessible(true);
            MySingleton4 t  = (MySingleton4) c.newInstance(null);
            System.out.println("两个实例地址是否相等:"+ (t==MySingleton4.getInstance()));
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}

8.测试结果

get a singleton
get a singleton
get a singleton
get a singleton
get a singleton
get a singleton
两个实例地址是否相等:false

说明
实现中均以做出具体说明

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值