java模式–单例模式
*创建一个实例,不对外开放创建实例的权利*
- 把自己的够着方法设置为private,不让别人new实例。
- 提供static方法给别人获取你的实例。
懒汉式
//单例调用getSingleton()生成SingleTon对象
Singleton singleton = Singleton.getSingleton();
public class Singleton {
private static Singleton singleton;
private Singleton(){
}
public static Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
饿汉式
//调用getInstance()创建对象
private static final Singleton SINGLETON = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return SINGLETON;
}
区别
- lazy需要时创建 第一次调用先执行getSingleton(),再执行构造方法
- hungry直接创建 第一次调用先执行构造方法,再getInstance()
- 简单的lazy线程不安全
- 当多线程并发执行getSingleton()时,可能出现多个实例。
- 线程1:在判空后进行初始化
- 线程2:在1未初始化完是进行另一个初始化
- 返回两个实例
- 当多线程并发执行getSingleton()时,可能出现多个实例。
线程(synchronized)安全型
//双重检验锁
private static volatile Singleton getSingleton;
public static Singleton getGetSingleton(){
if(getSingleton ==null){
synchronized (Singleton.class){
if(getSingleton ==null){
getSingleton = new Singleton();
}
}
}
return getSingleton;
}
当有多有线程调用getInstance()方法的时候,不管三七二十一,先让他们进来。如果fileIO实例不为空,那最好了,直接return实例singleton,跟synchronized一点都扯不上关系,所以也不会影响到性能。这是双重检验中的第一次检验。
如果Singleton是null的,就进入synchronized语句块,在synchronized语句块里面初始化对象。
当有多个线程通过第一次检验时,假设线程拿到锁进入synchronized语句块,对singleton实例进行初始化,释放Singleton.class锁之后,线程二持有这个锁进入synchronized语句块,此时又对singleton对象就行初始化。所以在这里进行第二次检验防止这种意外发生。
为什么要用volatile关键字修饰?
我们假设线程一进入第二次检验之后就执行Singleton singleton = new Singleton()操作,在这个操作中,JVM主要干了三件事
在堆空间里分配一部分空间;
- 执行Singleton的构造方法进行初始化;
把singleton对象指向在堆空间里分配好的空间。
但是,当我们编译的时候,编译器在生成汇编代码的时候会对流程顺序进行优化。优化的结果是有可能按照1-2-3顺序执行,也可能按照1-3-2顺序执行。
我们知道,执行完3的时候就fileIO对象就已经不为空了,如果是按照1-3-2的顺序执行,恰巧在执行到3的时候(还没执行2),突然跑来了一个线程,进来getInstance()方法之后判断singleton不为空就返回了singleton实例。
此时singleton实例虽不为空,但它还没执行构造方法进行初始化。又恰巧构造方法里面需要对某些参数进行初始化。后来闯进来的线程糊里糊涂对那些需要初始化的参数进行操作就有可能报错奔溃了。
序列化和反序列化
- 反射调用构造方法初始化新的实例
- Singleton singleton = Singleton.class.newInstance();
序列化和反序列化产生新的实例
对于通过反射调用构造方法的破坏方式我们可以通过在增加全局变量flag,在第一次初始化的时候就设置为true,第二次初始化的时候判断到flag为true就抛出异常。但这种办法也只能避免破坏,无法彻底阻止,因为他们可以反射flag来修改flag的值。
对于使用序列化和反序列化产生新的实例的方式就容易避免了,可以增加readResolve()方法来预防。
最后 使用枚举来实现单例模式 保证线程安全、反射安全以及反序列化安全
package org.apache.myfaces.blank;
import java.io.File;
/**
* Created by Forgot on 2017/6/6.
*/
public enum FileTest {
INSTANCE,TWO;
public File getFile(String filepath){
return new File(filepath);
}
public String setFile(){
return "3434342";
}
}
调用方式
System.out.println(FileTest.INSTANCE.setFile());
System.out.println(FileTest.TWO.setFile());