单例模式—饿汉式、懒汉式 超详细解析
【单例模式】:一个类在Java虚拟机中只有一个对象,并提供一个全局访问点,使对象具有了唯一性举例:数据库连接对象、线程池、缓存、日志对象创建方式:懒汉式、饿汉式【懒汉式】class HungrySingleton{private static HungrySingleton hungry = new HungrySingleton();private HungrySingleton(){};pub
【单例模式】:一个类在Java虚拟机中只有一个对象,并提供一个全局访问点,使对象具有了唯一性
举例:数据库连接对象、线程池、缓存、日志对象
创建方式:懒汉式、饿汉式
【饿汉式】
class HungrySingleton{
private static HungrySingleton hungry = new HungrySingleton();
private HungrySingleton(){};
public static HungrySingleton getInstance(){
return hungry;
}
}
饿汉式将构造器私有化,避免类在外部被实例化,外部只能通过get方法获取唯一实例。由于在类加载时就创建单例对象,属于天然的线程安全。
【懒汉式】
普通的懒汉式是线程不安全的,如下:
class LazySingleton{
private static LazySingleton instance;
private LazySingleton(){};
public static LazySingleton getInstance(){
if(instance==null) {
instance = new LazySingleton();
}
return instance;
}
}
如果多个线程同时获取单例getInstance,可能创建出多个单例对象:
方案1. 在getInstance方法上加锁
public static synchronized LazySingleton getInstance(){
if(instance==null) {
instance = new LazySingleton();
}
return instance;
}
每次获取对象都要上锁,性能较差,不推荐
方案2. 双检查锁DCL
class Singleton {
private static volatile Singleton singleton;
private Singleton(){};
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这样做的好处是一开始判断单例是否存在,避免了每次上锁,不存在则只在第一次上锁。
注意,这里使用volatile修饰单例对象是为了避免操作系统在进行new Singleton()这个非原子操作时指令重排序,具体步骤是:
1.分配内存空间
2.执行构造方法,初始化对象
3.将对象指向该内存空间
重排序1-2-3–》1-3-2
此时若线程2执行getInstance发现单例对象已有指针(不为null),但实际上内存空间可能还未存放对象,导致线程2后序操作报错。
方案3. 静态内部类
class SingletonInterClass{
private SingletonInterClass(){};
private static class InnerClass{
private static final SingletonInterClass SINGLETON = new SingletonInterClass();
}
public static final SingletonInterClass getInstance(){
return InnerClass.SINGLETON;
}
}
事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。
那么有没有什么措施呢?
private Singleton(){
synchronized (Singleton.class){
if(singleton!=null){
throw new RuntimeException("不要用反射破坏单例");
}
}
}
我们在构造器里加一个检测+抛出异常,就能阻止反射创建对象,最后是三重检测。
问题又来了,如果一开始没有创建单例,两个对象都用反射创建,一样可以破坏啊。别急,我们换一种检测:
private static boolean qinjiang = false;
private Singleton(){
synchronized (Singleton.class){
if(!qinjiang){
qinjiang = true;
}else{
throw new RuntimeException("不要用反射破坏单例");
}
}
}
可是,加入通过反编译获取到了标志变量,并通过反射机制修改,还是可以破解的
魔高一尺,道高一丈
枚举类型可以防止反射破坏单例
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
private EnumSingle() {
}
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE;
//反射
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
EnumSingle instance3 = constructor.newInstance();
System.out.println(instance1 == instance2);
System.out.println(instance1 == instance3);
}
第一个比较结果为true,第二个方法抛出异常,但这并不是我们想要看到的异常信息,而是下面的绿字部分
通过jad反编译可以看出实际被调用的是一个有参构造器,重新获取构造器
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
EnumSingle instance3 = constructor.newInstance();
再次运行,异常如下:
不能通过反射创建枚举类,这就是
以上就是我总结的关于饿汉式和懒汉式的常见方法,欢迎大神补充…
更多推荐
所有评论(0)