研磨23种大话设计模式------单例设计模式

大家好,我是一位在java学习圈中不愿意透露姓名并苟且偷生的小学员,如果文章有错误之处,还望海涵,欢迎多多指正

如果你从本文学到有用的干货知识,请帮忙点个赞呗,据说点赞的都拿到了offer

问题引申:

单例模式—对象的创建问题
类 :一个抽象笼统的概念 描述一组对象
对象:可以有好多个 具体的个体
举例:百度搜索方法 通过对象.搜索();方法的执行—临时空间
为了有效节省内存空间的占用—让当前的类只能创建一个对象

单例模式的四种实现方法:

方法一:饿汉式(立即加载的形式)
分析过程:

对象的创建—调用类中的构造方法
1. 构造方法私有

当前类的内部创建一个对象
属性 方法 构造方法 代码块—(没有返回值)
2. 创建私有的 静态的当前类对象作为属性 一片你来存储唯一的一个对象(上来就new)

StackOverFlowError—开辟对象空间时 调用构造方法 执行栈内存的临时空间
由于栈内存远比堆内存小故栈内存先发生溢出
由此联想到静态元素 属性 方法 代码块 内部类(只有一份)
3. 设计一个公有的静态方法 用来将唯一的这个对象返回回去

防止多线程并发
4.设计时加上synchronized关键字,其具体作用下面将会介绍

package singleton;

//单例设计模式
public class SingletonPattern {
    //饿汉式(立即加载的形式)
    private static SingletonPattern singleton = new SingletonPattern();
    private SingletonPattern(){}
    //方法名叫 getInstance 也可以   符合规范的写法(见名知意)
    public static synchronized SingletonPattern getSingleton(){
        return singleton;
    }
}

方法二:懒汉式(延迟加载的形式)
分析过程同方法一
  1. 将new的过程改为在返回方法中
package singleton;

//单例设计模式
public class SingletonPattern {

    //懒汉式(延迟加载的形式)
    private static SingletonPattern singletonPattern;
    private SingletonPattern(){}
    public static synchronized SingletonPattern getInstance(){
        if(singletonPattern == null){
            singletonPattern = new SingletonPattern();
        }
        return singletonPattern;
    }
}

补充:以上两种模式使用到synchronized是放在方法上的,故每次方法调用都需要获取锁和释放锁,锁的开销很大

方法三:双重检查锁定模式
分析过程:

可以理解为在懒汉式的基础上做了一个变化
需要用到的知识补充:
volatile
不稳定的
修饰属性
1.属性在某一个想成操作的时候被锁定 其他的线程没法获取属性
2.属性被某一个线程修改之后 另外的线程立即可见(即可见性)
3.可以禁止指令重新排布
synchronized(两个关键字都是与线程安全有关)
同步的
修饰方法 或者 方法内的某一段代码
1.当synchronized修饰的部分被线程访问时,这个线程会被锁住

并发编程的三个条件:

1.原子性:要实现原子性方式较多,可用synchronized,lock加锁,AtomicInteger等,但volatile关键字是无法保证原子性的;
2.可见性:要实现可见性,也可用synchronized,lock,volatile关键字可用来保证可见性;
3.有序性:要避免指令重排序,synchronized,lock作用的代码块自然是有序执行的,volatile关键字有效的禁止了指令重排序,实现了程序 执行的有序性;

模拟不加volatile关键字可能会出现的情况:(代码如下)
public class Singleton {
    static Singleton instance
 	private Singleton(){}
    public static Singleton getInstance(){
       	if (instance == null) {
           	synchronized(Singleton.class) {
                if (instance == null) {
                	instance = new Singleton();
                }
            }	
       	}
       	return instance;
	}
}
分析原因:

假设现在有两个线程A和B,A现在先进入了第2个if判断并执行第14行代码对对象进行初始化。A线程进入到构造方法对对象进行初始化时,由 于JVM会对指令进行重排,所以初始化的代码顺序可能是:

第一种顺序:
1,分配一块内存 M;
2,在内存 M 上初始化 Singleton 对象(对属性o进行初始化和赋值);
3,然后 M 的地址赋值给 instance 变量。

补充:事实上2,3可能发生重排序,但1不会,因为2,3指令需要依托1指令的执行结果

第二中顺序(实际上优化后的执行路径可能是这样的):
1,分配一块内存 M;
2,将 M 的地址赋值给 instance 变量;
3,最后在内存 M 上初始化 Singleton 对象(对属性o进行初始化和赋值)。

执行情况假设:假设虚拟机对指令重排优化后,按第二种顺序执行。A线程现在执行到第二步将 M 的地址赋值给 instance 变量;现在cpu对线程切换并切换到B线程上,B此时若走到第一个if判断,此时instance已不为空,所以直接返回instance(此时A线程还没有完成对instance变量引用的对象完成初始化也没有执行到第16行对对象锁进行释放)。因为此时B返回的instance的属性o没有被初化或赋值,所以B线程返回的instance对象o属性为空,如果B线程对返回的instance对象的o属性进行方法调用可能出现空指针异常。
若其他执行情况可逐一分析(如A线程先执行但还没给instance赋值,此时B判断第一个if为空成立进而在synchronized外等待A释放锁,当A出来时,B肯定要再次判断第二个if,不能进来就new,因为A已经初始化instance变量并将对象地址赋值给了instance变量)

因此解决办法:对instance加上volatile关键字。

原因:volatile可以禁止JVM和处理器对指令进行重排。

总结:这个模式缩小锁的范围,减少锁的开销,同时对象的创建可能发生指令的重排序,使用volatile可以禁止指令的重排序,保证多线程环境内的系统安全

方法五:生命周期托管(单例对象别人帮我们处理,类似于Spring框架中的IOC对象控制权反转)
分析过程:

自己创建一个托管类帮我们创建对象(单例的),我们只需要提供类全名(包名.类名)即可,这个方法通用,任何一个类想要实现单例且不用自己创建对象都可以找这个托管类

单例类实现代码:
package singleton;

//单例设计模式
public class SingletonPattern {

    //生命周期托管
    public SingletonPattern(){}
    //测试方法
    public void test(){
        System.out.println("黑夜问白天");
    }
}

托管类实现代码:
package singleton;

import java.util.HashMap;

//单例模式之生命周期托管
public class MySpring {

    //创建一个map集合记录每个类的实例对象 map方便找寻对应的那个实例对象
    //采用map集合是为了方便根据类全名寻找对应的对象
    private static HashMap<String,Object> beanBox = new HashMap<String,Object>();

    //设计一个方法 给定任何一个类全名(参数) 返回一个对应的实例对象(返回值)
    //第一个T 表示是泛型  接返回值得时候不需要造型,用什么类型来接就是什么类型(相当于将值返回时就帮你造了个型)
    //第二个T 表示返回的是T类型的数据
    public static <T>T getBean(String className){
        T obj = null;
        try {
            //1.直接从beanBox集合中获取
            obj = (T)beanBox.get(className);
            //2.如果obj是null 证明之前没有创建过对象
            if(obj == null){
                //3.通过类名字获取Class
                Class clazz = Class.forName(className);
                //4.通过反射创建一个对象
                obj = (T)clazz.newInstance();
                //5.将新的对象存入集合
                beanBox.put(className,obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}

主方法代码:
package singleton;

public class TestSingleton {

    public static void main(String[] args){
        SingletonPattern singletonPattern = MySpring.getBean("singleton.SingletonPattern");
        singletonPattern.test();
    }
}

其实还可以通过枚举来实现单例设计模式,不过枚举用的不多,感兴趣的小伙伴可以自己试试哦
结束:

之后开始慢慢的更新每一种设计模式,通过生动形象的生活现象举例带你感受设计模式的世界,其实设计模式不难,只是当我们面对某个场景时想不到用哪个设计模式该不该用设计模式…
博客内容来自腾讯课堂渡一教育拓哥,以及自己的一些理解认识,同时看了其他大牛写的设计模式技术文章综合总结出来的

猜你喜欢

转载自blog.csdn.net/bw_cx_fd_sz/article/details/108011510