设计模式一单例模式
欢迎光临我的博客[http://poetize.cn],前端使用Vue2,聊天室使用Vue3,后台使用Spring Boot
创建型模式
主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”
单例(Singleton)模式:
某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,
其拓展是有限多例模式。
原型(Prototype)模式:
将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
工厂方法(FactoryMethod)模式:
定义一个用于创建产品的接口,由子类决定生产什么产品。
抽象工厂(AbstractFactory)模式:
提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
建造者(Builder)模式:
将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,
最后构建成该复杂对象。
一.使用synchronized实现单例模式
实现Serializable ,被transient修饰,不能被序列化
一个静态变量不管是否被transient修饰,均不能被序列化
volatile是变量修饰符,其修饰的变量具有可见性。
可见性也就是说一旦某个线程修改了该被volatile修饰的变量,
它会保证修改的值会立即被更新到主存,
当有其他线程需要读取时,可以立即获取修改之后的值。
volatile禁止指令重排
synchronized可作用于一段代码或方法,既可以保证可见性,又能够保证原子性。
单例模式双重校验锁使用synchronized,为什么同时使用volatile?
synchronized虽然保证了原子性,但却没有保证指令重排序的正确性,
会出现A线程执行初始化,但可能因为构造函数里面的操作太多了,
所以A线程的Instance实例还没有造出来,但已经被赋值了。
而B线程这时过来了,错以为Instance已经被实例化出来,
一用才发现Instance尚未被初始化。
要知道我们的线程虽然可以保证原子性,但程序可能是在多核CPU上执行。
《指令重排》
当instance不为null时,仍可能指向一个"
被部分初始化的对象
"
。
问题出在这行简单的赋值语句:instance = new Singleton();
它并不是一个原子操作。事实上,它可以”抽象“为下面几条JVM指令:
memory = allocate(); //1:分配对象的内存空间
initInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
上面操作2依赖于操作1,但是操作3并不依赖于操作2,
所以JVM可以以“优化”为目的对它们进行重排序
,经过重排序后如下:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址(此时对象还未初始化)
ctorInstance(memory); //2:初始化对象
1 class Singleton1 implements Serializable { 2 3 private static volatile Singleton1 INSTANCE = null; 4 5 private Singleton1() { 6 //解决反射问题 7 if (INSTANCE != null) { 8 throw new RuntimeException("已存在实例"); 9 } 10 } 11 12 //防止序列化攻击, 13 //当JVM从内存中反序列化地"组装"一个新对象时, 14 // 就会自动调用这个 readResolve方法来返回我们指定好的对象了,单例规则也就得到了保证。 15 private Object readResolve() { 16 return INSTANCE; 17 } 18 19 public static Singleton1 getInstance() { 20 //双重检验锁模式 21 if (INSTANCE == null) { //Single Checked 22 synchronized (Singleton1.class) { 23 if (INSTANCE == null) //Double Checked 24 INSTANCE = new Singleton1(); 25 } 26 } 27 return INSTANCE; 28 } 29 }
二.使用静态内部类实现单例模式
1.调用外部类的静态变量和静态方法可以让外部类被加载到内存中,
不过被调用的外部类的内部类(不论是否静态)不会被加载。
2.加载静态内部类之前会先加载外部类,静态内部类或非静态内部类在使用它们的成员时才加载。
3.内部类可以随意使用外部类的成员对象和方法(即使私有),而不用生成外部类对象
final修饰的方法是不能被重写的,但是可以重载。
1 class Singleton2 { 2 3 private static class SingletonCreater { 4 //由于实例被声明为静态和final变量了,在第一次加载类在内存中就会初始化,所以创建实例本身是线程安全的 5 private static final Singleton2 INSTANCE = new Singleton2(); 6 } 7 8 private Singleton2() { 9 } 10 11 public static final Singleton2 getInstance() { 12 //当线程调用getInstance方法,由于调用了静态内部类的成员,会使内部类被加载到内存, 13 //而内部类的成员此时也被加载并初始化了,这样返回的就是外部类的实例了 14 return SingletonCreater.INSTANCE; 15 } 16 }
三.枚举实现单例模式
1.避免序列化问题
2.避免反射攻击
1 enum Singleton3 { 2 INSTANCE; 3 4 public void printHello() { 5 System.out.println("Hello"); 6 } 7 8 public static void main(String[] args) throws Exception { 9 Singleton3 instance = Singleton3.INSTANCE; 10 instance.printHello(); 11 } 12 }