设计模式 原型

1)、定义

原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
属于创建型模式。

2)、适用场景

1、类初始化消耗资源较多。
2、new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
3、构造函数比较复杂。
4、循环体中生产大量对象时,可读性下降。

2)、常见写法

(1)、饿汉式单例

饿汉模式在类被初始化时就已经在内存中创建了对象,以空间换时间,故不存在线程安全问题。

1
2
3
4
5
6
7
8
9
10
11
12
public class HungrySingleton {
//先静态、后动态
//先属性、后方法
//先上后下
private static final HungrySingleton hungrySingleton = new HungrySingleton(); //有final,防止别处修改值。

private HungrySingleton(){}

public static HungrySingleton getInstance(){
return hungrySingleton;
}
}

(2)、懒汉式单例

被外部类调用时才创建实例

1、懒汉式

1
2
3
4
5
6
7
8
9
10
11
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
//静态块,公共内存区域
private static LazySimpleSingleton lazy = null; //没final,在全局访问点赋值。
public synchronized static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}

线程类:

1
2
3
4
5
6
7
8
9
public class ExectorThread implements Runnable{
@Override
public void run() {
LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
// ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
// LazyDoubleCheckSingleton singleton = LazyDoubleCheckSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" + singleton);
}
}

测试类:

1
2
3
4
5
6
7
8
9
public class LazySimpleSingletonTest {
public static void main(String[] args) {
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("End");
}
}

synchronized加锁,在线程数量比较多情况下,大批量线程会出现阻塞,从而导致程序运行性能大幅下降。
既兼顾线程安全又提升程序性能的方式:

2、双重检查锁的单例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null;

private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
if(lazy == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazy == null){
lazy = new LazyDoubleCheckSingleton(); //1.分配内存给这个对象。2.初始化对象。3.设置lazy指向刚分配的内存地址。
}
}
}
return lazy;
}
}

执行双重检查是因为,如果多个线程同时了通过了第一次检查,并且其中一个线程首先通过了第二次检查并实例化了对象,那么剩余通过了第一次检查的线程就不会再去实例化对象。这样,除了初始化的时候会出现加锁的情况,后续的所有调用都会避免加锁而直接返回,解决了性能消耗的问题。
不加volatile的隐患:
有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,访问到的是一个初始化未完成的对象。
为了解决上述问题,需要在lazy前加入关键字volatile。使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

3、内部类的单例模式:

懒加载。线程安全。
加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LazyInnerClassSingleton {

private LazyInnerClassSingleton(){
if(LazyHolder.LAZY != null){ //为了防止暴力初始化,此处需要判断。
throw new RuntimeException("不允许创建多个实例");
}
}

//static 是为了使单例的空间共享。final保证这个方法不会被重写,重载。
public static final LazyInnerClassSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}

//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}

暴力初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LazyInnerClassSingletonTest {
public static void main(String[] args) {
try{
Class<?> clazz = LazyInnerClassSingleton.class;
Constructor c = clazz.getDeclaredConstructor(null); //通过反射拿到私有的构造方法
c.setAccessible(true);
Object o1 = c.newInstance(); //暴力初始化
Object o2 = c.newInstance(); //调用了两次构造方法,相当于new了两次
System.out.println(o1 == o2);
}catch (Exception e){
e.printStackTrace();
}
}
}

(3)、注册式单例

(4)、ThreadLocal单例

3)、优点

4)、缺点

4)、案例

Calandar.getInstance()
LoggerFactory.getLogger()
是简单工厂同时也是单例模式。
单例:
ServletContext、ServletConfig、ApplicationContext DBPool

本文由 lilyssh创作。可自由转载、引用,但需署名作者且注明文章出处。


当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器