最近加入了图灵学院java架构师的学习,着重学习高并发分布式核心架构技术学习,觉得很不错,分享架构大纲和总结的设计模式给大家,想要提升的同学可以在这些方面下点功夫,
课程资料
一:概念及理解
二、单例分类及详解
1、饿汉式单例
2、懒汉式单例
3、注册式单例
4、ThreadLocal单例
单例模式小结:
单例模式关键点:
构造方法私有化(保证对象只能自己来创建)
自己在内部创建自己的实例
提供一个全局的访问点(一般是static方法)是拿到单例的入口
在单例类首次加载时就立即初始化,并且创建单例对象。不管有没有使用,先创建了再说。
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;
}
}
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费内存空间,如果从来没有使用也都创建了,小范围内影响不大,但是大范围使用不建议使用。
为了改善内存浪费,我们可以在使用到单例实例时再创建,这就时下面的懒汉式单例
特点:当用户要使用单例时才创建对象。(线程不安全)
//懒汉式单例
//在外部需要使用的时候才进行实例化
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;
}
解决了序列化问题,最安全单例;
每一个实例都登记到某一个地方,使用唯一的标识获取实例,一种为容器缓存,一种为枚举登记,下面介绍其中一种;
//枚举式单例
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;
}
}
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();
}
}
单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。
重点掌握饿汉式与懒汉式两种单例模型。
饿汉式:不用也加载。线程安全的,可以直接用于多线程而不会出现问题,但会出现内存浪费情况,不推荐大范围使用
懒汉式:用时再加载。非线程安全,可通过getInstance方法上加同步synchronized、双重检查锁、静态内部类三种方式改善;
其中第一种每次都同步,性能不好,第二种两次非空检查,避免每次同步的性能损耗,第三种没有性能损耗。