转载

谈谈你知道的设计模式? - 《java核心技术》笔记

分类

按照模式的应用目标大致分类:

  • 创建型模式:是对对象创建过程的问题和解决方案的总结,比如单例、工厂、构建器、原型。
  • 结构型模式:针对软件设计结构的总结,关注于类、对象继承、组合方式的实践经验,比如适配器、装饰者、桥接、代理、组合、外观、享元。
  • 行为型模式:从类或对象之间交互、职责划分等角度总结等模式。比如策略、解释器、命令、观察者、迭代器、模版方法、访问者。

例子

装饰器

通过 识别类设计特征 来进行判断,其类构造函数以 相同的 抽象类或者姐口味输入参数。

public BufferedInputStream(InputStream in)

谈谈你知道的设计模式? - 《java核心技术》笔记

构建器

比较优雅的解决构建复杂对象的麻烦,这里的“复杂”是指类似需要输入的参数组合较多,如果用构造函数,则往往需要为每一种可能的输入参数组合实现相应的构造函数。

HttpRequest request = HttpRequest.newBuilder(new URI(uri))
                     .header(headerAlice, valueAlice)
                     .headers(headerBob, value1Bob,
                      headerCarl, valueCarl,
                      headerBob, value2Bob)
                     .GET()
                     .build();

单例

版本一:

public class Singleton {
       private static Singleton instance = new Singleton();
       public static Singleton getInstance() {
          return instance;
       }
    }

有问题,没有把构造函数声明为private的

版本二:private化构造函数+懒加载。

public class Singleton {
        private static Singleton instance;
        private Singleton() {
        }
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
        return instance;
        }
    }

问题:多线程不可用,比如考虑以下情况:

Time | Thread A | Thread B

T1 | 检查到uniqueSingleton为空 |

T2 | | 检查到uniqueSingleton为空

T3 | | 初始化对象A

T4 | | 返回对象A

T5 | 初始化对象B |

T6 | 返回对象B |

版本三:双检锁+volatile

public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton() {
    }

    public static Singleton getSingleton() {
        if (singleton == null) { // 尽量避免重复进入同步块
            synchronized (Singleton.class) { // 同步.class,意味着对同步类方法调用
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

volatile关键字的作用:

实例化对象的顺序可以分为:

  1. 分配内存空间。
  2. 初始化对象。
  3. 将对象指向刚分配的内存空间。

但是有些编译器为了性能的原因,可能会进行 重排序

  1. 分配内存空间。
  2. 将对象指向刚分配的内存空间。
  3. 初始化对象。

这样考虑两个线程发生了以下调用:

Time | Thread A | Thread B

T1 | 检查到uniqueSingleton为空 |

T2 | 获取锁 |

T3 | 再次检查到uniqueSingleton为空 |

T4 | 为uniqueSingleton分配内存空间 |

T5 | 将uniqueSingleton指向内存空间 |

T6 | | 检查到uniqueSingleton不为空

T7 | | 访问uniqueSingleton(此时对象还未完成初始化)

T8 | 初始化uniqueSingleton |

这样子在T7时刻线程B访问到了一个初始化为完成的对象。

volatile保证重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

总结:

  • volatile提供可见性,以保证getInstance返回的是初始化完全的对象;
  • 在加锁前检查,尽量避免进入相对昂贵的同步快;
  • 在class级别同步,保证现场安全的类方法调用。

版本四:使用静态内部类

理论依据是对象初始化过程中隐含的初始化锁。该模式被认为是替代双边检的最佳方式。

public class Singleton {
	private Singleton(){}
	public static Singleton getSingleton(){
    	return Holder.singleton;
	}
    /**
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
     */
	private static class Holder {
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
    	private static Singleton singleton = new Singleton();
	}
}
原文  http://yizhanggou.top/tan-tan-ni-zhi-dao-de-she-ji-mo-shi-java/
正文到此结束
Loading...