单例模式

一.简介

  最近写软件体系结构课程总结的时候,看到了设计模式,当时只简单背了一下概念,现在正好有空写一下代码的实现
  特点:一个类只有一个实例,并只提供一个访问它的全局访问点。可以在一定程度上减少资源浪费,避免不一致的状态
  应用:缓冲池,文件管理,设备处理器等等
  实现方式、关键字:饿汉式,懒汉式,synchronized,volatile等

二.实现

/**
 * @Author Yuan
 * @Date 2022 02 01 21:50
 * @Description: 项目名称 design-pattern 路径 com.sjy.designpattern.single
 * @Function: 单例模式饿汉式实现
 */

public class ehan {
    private static final ehan instance=new ehan();

    //构造方法私有化
    private ehan(){};

    //获取实例的方法,永远只能获取到静态变量instance
    public static ehan getInstance(){
        return instance;
    }
    //此方法最大的问题在于,类初始化时静态变量就已经分配资源了
    //而不是在类的实例化时分配资源,会导致资源浪费
    //线程安全,因为返回的永远是静态变量instance
}

/**
 * @Description: 在第一次调用获取实例方法时分配内存,实现了懒加载,非线程安全;
 * @Function: 基本懒汉式的实现,线程不安全
 */
public class basiclanhan {
    private static basiclanhan instance=null;
    private basiclanhan(){};
    public static basiclanhan getInstance(){
        //多线程情况下,多个线程同时判断instance是否为空,都得到true,进入方法体,会多次new对象
        if(instance==null) instance=new basiclanhan();
        return instance;
    }
}

/**
 * @Description: synchronized对获得实例方法加锁,实现多线程安全
 * @Function: 懒汉式变种(synchronized),线程安全
 */
public class synchronizedlanhan {
    private static synchronizedlanhan instance=null;
    private synchronizedlanhan(){};
    //直接在获得实例方法处加锁,阻塞对应线程
    public static synchronized synchronizedlanhan getInstance(){
        //效率过低,当实例已不为null时,完全没有必要阻塞,直接返回即可,严重浪费资源
        if(instance==null) instance=new synchronizedlanhan();
        return instance;
    }
}

/**
 * @Description: 多进行一次if判断,判空后再加锁,存在指令重排的问题
 * @Function: 基本双检锁的实现,线程安全
 */
public class basicdcl {
    private static basicdcl instance=null;
    private basicdcl(){};
    public static basicdcl GetInstance(){
        //不为空的话直接返回对象即可,节约资源
        if(instance==null){
            //若对象为空,再利用锁的机制,确保单例
            synchronized (basicdcl.class){
                //为什么要再判断一次呢?
                //因为该线程执行时对象为null,之后没有获得锁,其余线程获得锁后实例化,此时若不判断则会生成多个实例
                if(instance==null) instance=new basicdcl();
            }
        }
        return instance;
    }
}

/*  指令重排:编译器生成汇编代码时会对指令顺序进行调整,例如查看循环语句的汇编会发现
	while和for循环都会被优化成do-while,汇编代码中为loop goto,可以提升指令执行的效率

	可见性:程序执行时获得变量可以从CPU高速缓存(Cache)或内存中获取,因此会导致数据的不一致性
	多个线程操作同一个变量时,会将其先拷贝到各自独有的运行主存中,再操作得到结果
	volatile关键词的作用在于:
      1)该变量立即刷新到主内存。写操作后插入一个写屏障指令,读操作前插入一个读屏障指令。
      2)使其他线程的共享变量立即失效。言外之意当其他线程需要的时候再从主内存取。
	由于该变量均从主存中读取,因此可以确保变量的可见性
*/

/*
 * @Description: basicdcl的优化版本。利用volatile确保可见性
 * @Function: 双检测的实现,线程安全
 */
public class volatiledcl {
    private static volatile volatiledcl instance=null;
    private volatiledcl(){};
    public static volatiledcl getInstance(){
        if(instance==null){
            synchronized (basicdcl.class){
                if(instance==null) instance=new volatiledcl();
            }
        }
        return instance;
    }
}

/*
 * @Description: 静态内部类的实现方式,实现了懒加载与线程安全
 * @Function: 利用静态内部类的特性,同懒汉式相似,实现了线程安全
 */
public class Singleton {

    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }
	//静态内部类不会在Singleton类加载时就加载,而是在调用getInstance()方法时才进行加载,达到了懒加载的效果

    private Singleton() {}
	//线程安全的原理同饿汉式相似
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

三.总结

  上文介绍了单例模式的六种实现方式,各有各的优点与缺点,除这六种方式外,还可以通过枚举的方式实现,枚举方式利用了枚举的特性,让JVM帮我们保证线程安全和单一实例的问题。这种方式虽然使用较少,但却是单例模式最好的实现方式

end
  • 作者:Yuan(联系作者)
  • 发表时间:2022-02-01 23:01
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转载博主转载的文章,请附上原文链接
  • 公众号转载:请联系作者
  • 评论