java 设计模式 之单例模式

admin 2020年05月05日 •  IntelliJ IDEA 教程 907
本文最后修改于 405 天前,部分内容可能已经过时!

1.单例模式

单例模式分 饿汉式和DCL 懒汉式

饿汉式

程序启动时就把对象创建完成,如果方法内有参数,并初始化参数

package com.yuan.single;

/**
 * @Author king
 * @Date 2020/5/5 10:26
 * @Version 1.0
 */
//饿汉式单例模式

public class Hungry {

    //TODO:浪费内存  假如这几个byte 程序启动时被对象创建,会非常耗内存或者资源  
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];

    private Hungry(){

    }
    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}

饿汉式会存在 浪费空间现象

那么久延伸出了懒汉式。

懒汉式

懒汉式 不会程序运行时而创建,用的时候才会去创建,

package com.yuan.single;

/**
 * @Author king
 * @Date 2020/5/5 10:46
 * @Version 1.0
 */
//懒汉式单例模式
public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");

    }
    
    private static LazyMan lazyMan;

    public static   LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

但是:单例模式,在单线程下是没有问题的如果存在多线程那么问题就出现了 存在多线程的话

    public static void main(String[] args) {
        for (int i = 0; i <10 ; i++) {
            new Thread(new Runnable() {
                public void run() {
                    LazyMan.getInstance();
                }
            }).start();
        }
    }

加一个多线程测试

image-20200505141911531

运行测试没次都不一样。

解决方法

·加锁

//双重检测锁模式 懒汉式单例模式 DCL
    public static   LazyMan getInstance(){
        if (lazyMan==null) {
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

加上 双重检验锁

?思考 这样就安全了么?

lazyMan = new LazyMan();

这步操作在极端情况下绝对是有问题的为什么?

因为他不是原子性操作

new一个对象时的操作:

  1. 分配内存空间
  2. 执行构造方法。初始化对象
  3. 把这个对象指向这个空间,

我们希望他按着顺序123执行

但是他也可能按着132顺序来执行,如果按照132执行

比如说 A线程进来了,执行1 3 2 执行完3之后 B线程到了。进行if判断 lazyMan对象不等于空(因为他有指向的空间)直接返回lazyMan 但是这个对象还是空值没初始化完成,这里就会引发问题。

解决方法:在参数上 volatile

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private volatile static LazyMan lazyMan;

    //双重检测锁模式 懒汉式单例模式 DCL懒汉式
    public static   LazyMan getInstance(){
        if (lazyMan==null) {
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();//非原子性操作
                    /*
                    * 1.分配内存空间
                    * 2.执行构造方法。初始化对象
                    * 3.把这个对象指向这个空间,
                    * */
                }
            }
        }
        return lazyMan;
    }
}

静态内部类

还有静态内部类

package com.yuan.single;

/**
 * @Author king
 * @Date 2020/5/5 14:46
 * @Version 1.0
 */
public class Holder {

    private Holder(){
    }
    
    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final  Holder HOLDER=new Holder();
    }
}

思考

以上三种模式一定就是安全的嘛?

java中有一门技术可以破坏他的单例性-------反射

image-20200505150027223

如图所示 两个对象指向的是同一个

利用反射来破坏他的单例性

 public static void main(String[] args) throws Exception {
        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//无视私有修饰
        LazyMan instance2 = declaredConstructor.newInstance();


        System.out.println("instance = " + instance);
        System.out.println("instance2 = " + instance2);


    }

运行结果如图

image-20200505150216494

如何解决呢

    private LazyMan(){
      synchronized (LazyMan.class){
          if (lazyMan!=null){
              throw new RuntimeException("不要试图使用反射破坏单例-异常抛出");
          }
      }
    }

在构造参数中添加synchronized 再次加锁

进行三次加锁

再次执行 抛出异常

image-20200505153552392

单例解决了吗? 还有没。

如果两次都使用反射来创建对象呢

两次都使用反射来创建对象 单例模式又被破坏了。

解决方法 可以通过创建一个标识位来标识类的创建

private static boolean yuanjian = false;

    private LazyMan(){
      synchronized (LazyMan.class){
          if (yuanjian==false){
              yuanjian = true;
          }else{
              throw new RuntimeException("不要试图使用反射破坏单例-异常抛出");
          }
      }
    }

可以通过设置一个字段标识

再次运行结果:

image-20200505154306001

这样解决了 通过反射创建对象的单例,但是

重点来了 但是!

他还是不安全的

假设通过某种方法(很多种方法比如反编译)知道了参数名还是可以通过反射来修改他

 public static void main(String[] args) throws Exception {
       //LazyMan instance = LazyMan.getInstance();

       Field yuanjian = LazyMan.class.getDeclaredField("yuanjian");
       yuanjian.setAccessible(true);//无视私有修饰符private
       Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
       declaredConstructor.setAccessible(true);//无视私有修饰符private
       LazyMan instance = declaredConstructor.newInstance();
       yuanjian.setBoolean(instance,false);

       LazyMan instance2 = declaredConstructor.newInstance();


       System.out.println("instance = " + instance);
       System.out.println("instance2 = " + instance2);


   }

运行结果如图

image-20200505155606543

单例模式还是被破坏了。

以上关于反射所有的问题都可以用枚举(Enum)来解决

枚举反射不能破坏。

image-20200505165236301

在反射的源码里有如果类是一个枚举类那么直接抛异常

使用枚举 没有无参构造

image-20200505165833285

Tags:java单例模式23种设计设计
上一篇
下一篇

添加新评论

已有 907 条评论

 pereCreelffup 7 个月前 • |

casino games online casino gambling slots online vegas slots online

 glodiulgageclal 7 个月前 • |

vegas casino slots online casino real money gold fish casino slots slot games online slots

 glodiulgageclal 7 个月前 • |

gold fish casino slots play slots real money casino casino play play casino

 glodiulgageclal 7 个月前 • |

online casinos world class casino slots free casino games online real casino slots

 glodiulgageclal 7 个月前 • |

casino play play casino casino game

 glodiulgageclal 7 个月前 • |

http://onlinecasinos911.com/# online slots online gambling real casino slots