图灵学院java架构师VIP课程学习总结


      最近加入了图灵学院java架构师的学习,着重学习高并发分布式核心架构技术学习,觉得很不错,分享架构大纲和总结的设计模式给大家,想要提升的同学可以在这些方面下点功夫,

      课程资料

一:概念及理解

二、单例分类及详解

1、饿汉式单例

2、懒汉式单例

3、注册式单例

4、ThreadLocal单例

单例模式小结:

单例模式关键点:


一:概念及理解

  • 单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。
  • 常见单例举例:ServletContext、ServletContextConfig ;在 Spring 框架应用中 ApplicationContext;数据库的连接池DBPool也都是单例形式。
  • 优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。避免对资源的多重占用。
  • 缺点:没有接口,不能继承,要修改只能修改代码
  • 单例共有特征:

构造方法私有化(保证对象只能自己来创建)

自己在内部创建自己的实例

提供一个全局的访问点(一般是static方法)是拿到单例的入口

二、单例分类及详解

1、饿汉式单例

在单例类首次加载时就立即初始化,并且创建单例对象。不管有没有使用,先创建了再说。

public class HungrySingleton {
	//先静态、后动态
	//先属性、后方法
	//先上后下
	private static final HungrySingleton hungrySingleton = new HungrySingleton();
	//构造方法私有化
	private HungrySingleton(){}
	//static全局访问点
	public static HungrySingleton getInstance(){
		return hungrySingleton;
	}
}

第二种饿汉式单例写法:利用static方法块,在类加载时就构建了唯一对象;

public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungrySingleton;

    static {
        hungrySingleton = new HungryStaticSingleton();
    }

    private HungryStaticSingleton(){}
    public static HungryStaticSingleton getInstance(){
        return hungrySingleton;
    }
}

优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。

缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费内存空间,如果从来没有使用也都创建了,小范围内影响不大,但是大范围使用不建议使用。

为了改善内存浪费,我们可以在使用到单例实例时再创建,这就时下面的懒汉式单例

2、懒汉式单例

特点:当用户要使用单例时才创建对象。(线程不安全)

//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    //静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;
    public static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

以上懒汉式单例的实现是线程不安全的,并发环境下很可能出现多个Singleton实例,不同线程进入到if判断语句当中时,会出现创建多个不同实例的情况,偏离单例设计模式的初衷。

改进方法:通过synchronized来解决。

第一种:在getInstance方法上加同步synchronized(线程安全)

public synchronized static LazySimpleSingleton getInstance(){
    if(lazy == null){
        lazy = new LazySimpleSingleton();
    }
    return lazy;
}

 缺点:在getInstance方法上用synchronized 加锁,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降。于是下面第二种是更好的方式,既兼顾线程安全又提升程序性能

第二种:双重检查锁的单例模式,在getSingleton方法中对singleton进行两次判空。(线程安全)

public static LazyDoubleCheckSingleton getInstance(){
    if(lazy == null){
        synchronized (LazyDoubleCheckSingleton.class){
            if(lazy == null){
                lazy = new LazyDoubleCheckSingleton();
            }
        }
    }
    return lazy;
}

更优方案:synchronized 关键字,总归是要上锁,对程序性能还是存在一定影响的。以下采用静态内部类的方式:

第三种:静态内部类方式(内部类先于外部类加载,调用getInstance方法时,内部类逻辑才会执行。性能最优的写法)(线程安全)

//这种形式兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题
//完美地屏蔽了这两个缺点
public class LazyInnerClassSingleton {
    //默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazyInnerClassSingleton(){}
    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static final LazyInnerClassSingleton getInstance(){
    //在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }
    //默认不加载
    private static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

高级内容:(这里只做大概说明,具体可参考我头部链接一起学习)

反射破坏单例

我们前面所介绍的,单例模式必须满足构造方法私有化,避免外部实例化,而Java反射机制是能够实例化构造方法为private的类的,使所有的Java单例实现失效。

解决办法:在构造函数中抛出异常,提醒开发人员,不允许用反射机制创建单例。

private LazyInnerClassSingleton(){
    if(LazyHolder.LAZY != null){
        throw new RuntimeException("不允许创建多个实例");
    }
}

序列化破坏单例

      当我们将一个单例对象创建好,序列化然后写入到磁盘,下次使用时再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例。

解决办法:在单例类中加上以下方法,覆盖了序列化对象,反序列化出来的对象,还是创建了两次,发生在JVM层次,之前反序列化出来的对象被GC回收。

private Object readResolve(){
    return INSTANCE;
}

3、注册式单例

解决了序列化问题,最安全单例;

每一个实例都登记到某一个地方,使用唯一的标识获取实例,一种为容器缓存,一种为枚举登记,下面介绍其中一种;

//枚举式单例
public enum EnumSingleton {
    INSTANCE;
    private Object data;
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

4、ThreadLocal单例

ThreadLocal 不能保证其创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。

public class ThreadLocalSingleton {
    private static final ThreadLocal threadLocalInstance =
        new ThreadLocal(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };
    private ThreadLocalSingleton(){}
    public static ThreadLocalSingleton getInstance(){
        return threadLocalInstance.get();
    }
}

单例模式小结:

单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。

单例模式关键点:

  • 构造函数方法为Private修饰。
  • 通过一个静态方法或者枚举返回单例类对象。
  • 需要确保单例类的对象有且只有一个,尤其是在多线程环境下。
  • 确保单例类对象在反序列化时不会重新构建对象。

重点掌握饿汉式与懒汉式两种单例模型。

饿汉式:不用也加载。线程安全的,可以直接用于多线程而不会出现问题,但会出现内存浪费情况,不推荐大范围使用

懒汉式:用时再加载。非线程安全,可通过getInstance方法上加同步synchronized、双重检查锁、静态内部类三种方式改善;

其中第一种每次都同步,性能不好,第二种两次非空检查,避免每次同步的性能损耗,第三种没有性能损耗。


   

你可能感兴趣的:(学习笔记,资料分享,java,软件架构师)