设计模式-Singleton的8种方式2种完美无瑕.

发布于 2021-01-29 01:44

  • 应用场景:只需要有一个实例

    各种Mgr

    各种Factory

单例第一步把构造方法设置成私有的,无法被new出来,要想用必须调用它的getInstance();


1.  第一种:

public class Mgr01 {  private static final Mgr01 INSTANCE = new Mgr01();  private Mgr01() {};  public static Mgr01 getInstance() { return INSTANCE;}  public void m() {System.out.println("m");}  public static void main(String[] args) {    Mgr01 m1 = Mgr01.getInstance();    Mgr01 m2 = Mgr01.getInstance();    System.out.println(m1 == m2);  }}

运行发现永远为true因为,getInstance出来的永远是static INSTANCE;

这个比较简单,实际上在工作中想不到其它时候就用这种特简单,类加载到内存后,就实例化了一个单例,JVM保证线程安全,推荐大家使用.

唯一缺点:不管用到与否,类加载的时候完成实例化,饿汉式.

2. 第二种:

public class Mgr02 {    private static final Mgr02 INSTANCE;  static {    INSTANCE = new Mgr02();  }    private Mgr02() {};    public static Mgr02 getInstance() {    return INSTANCE;  }  public void m() {    System.out.println("m");  }  public static void main(String[] args) {    // TODO Auto-generated method stub    Mgr01 m1 = Mgr01.getInstance();    Mgr01 m2 = Mgr01.getInstance();    System.out.println(m1 == m2);  }}

本质上和第一种没区别

3. 第三种:懒汉式加载

public class Mgr03 {  private static Mgr03 INSTANCE;  private Mgr03() {  }  public static Mgr03 getInstance() {    if(INSTANCE == null) {//      try {//        Thread.sleep(1);//      } catch (InterruptedException e) {//        // TODO Auto-generated catch block//        e.printStackTrace();//      }      INSTANCE = new Mgr03();    }    return INSTANCE;  }  public void m() {    System.out.println("m");  }  public static void main(String[] args) {    // TODO Auto-generated method stub    for(int i=0;i<100;i++) {      new Thread(()->{        System.out.println(Mgr03.getInstance().hashCode());      }).start();    }  }}

类加载时候没有new没有被实例化,只有在getInstance时候才实例化,称为“懒加载”,但是这里存在一个线程不安全的问题,多线程情况下,在if(INSTANCE == null)之后还没有new的时候,另外一个线程判断仍然为null,所以也进if里面了,那么就new出来两个线程,可以通过打印hashcode判断是否为同一个对象,不同对象的hashcode是不同的。由于线程执行较快,如果测试不出来可以加上sleep个1毫秒,就可以看出结果。

虽然比之前的饿汉式相比,修复了无伤大雅的初始化加载问题,但是但来了更严重的问题,随意不推荐这种。

4. 加锁synchronized 

public class Mgr04 {  private static Mgr04 INSTANCE;  private Mgr04() {}  public static synchronized Mgr04 getInstance() {    if(INSTANCE == null) {      try {        Thread.sleep(1);      } catch (InterruptedException e) {        e.printStackTrace();      }      INSTANCE = new Mgr04();    }    return INSTANCE;  }  public void m() {    System.out.println("m");  }  public static void main(String[] args) {    // TODO Auto-generated method stub    for(int i=0;i<100;i++) {      new Thread(()->{        System.out.println(Mgr04.getInstance().hashCode());      }).start();    }  }

加锁后发现虽然sleep线程,但hashcode始终一样,保证了线程的安全,但是效率下降。(synchronized 所的是对象)

5. 减小synchronized内容,试图提高效率

public class Mgr05 {  private static Mgr05 INSTANCE;  private Mgr05() {}  public static Mgr05 getInstance() {    if(INSTANCE == null) {      synchronized(Mgr05.class) {        try {          Thread.sleep(1);        } catch (InterruptedException e) {          // TODO Auto-generated catch block          e.printStackTrace();        }        INSTANCE = new Mgr05();      }    }    return INSTANCE;  }  public void m() {    System.out.println("m");  }  public static void main(String[] args) {    // TODO Auto-generated method stub    for(int i=0;i<100;i++) {      new Thread(()->{        System.out.println(Mgr05.getInstance().hashCode());      }).start();    }  }}

不在整个方法上加synchronized 只在需要的代码块加上它。

查看运行结果发现,没有保证线程的安全,出现多个hashcode值,原因在于 线程1 if(INSTANCE == null) 后还没有执行加锁,线程2  if(INSTANCE == null) 这时候实例为空也进来了,然后加锁,new 实例,释放锁后,线程1加锁new实例,导致多个实例产生。

6. 双层检查

public class Mgr06 {  private static Mgr06 INSTANCE;  private Mgr06() {  }  public static Mgr06 getInstance() {    if(INSTANCE == null) {      synchronized(Mgr06.class) {        if(INSTANCE == null) {          try {            Thread.sleep(1);          } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();          }          INSTANCE = new Mgr06();        }      }    }    return INSTANCE;  }  public void m() {    System.out.println("m");  }  public static void main(String[] args) {    // TODO Auto-generated method stub    for(int i=0;i<100;i++) {      new Thread(()->{        System.out.println(Mgr06.getInstance().hashCode());      }).start();    }  }}

两个或者多个线程进入第6行 if(INSTANCE == null) { 里面后,先执行的线程加锁判断是否为空,实例化,释放锁;之后的线程判断instance就不为空了,所以不会再次实例化.

有人会有疑问问 第一个if(INSTANCE == null) { 有没有必要,答案是有,因为可以减少加锁的次数。

很多人认为这种很完美,但是其实真的没必要,第一种挺好,但是生活中总有一些人追求完美。所以用第6种,除此之外呢,又诞生了另外两种写法。另外两种更好。

7.  最完美的写法之一

public class Mgr07 {  private Mgr07() {  }    private static class Mgr07Holder{    private final static Mgr07 INSTANCE = new Mgr07();  }    public static Mgr07 getInstance() {    return Mgr07Holder.INSTANCE;  }  public void m() {    System.out.println("m");  }  public static void main(String[] args) {    // TODO Auto-generated method stub    for(int i=0;i<100;i++) {      new Thread(()->{        System.out.println(Mgr07.getInstance().hashCode());      }).start();    }  }}

新增了静态内部类,一个类的静态内部类,在外层类被加载时候不会被加载。

调用getInstance时候,内部类才会被加载,不但实现了懒加载,还实现了只有一个实例。JVM保证的线程安全,虚拟机加载class时候只加载一次,所以内部类也只加载一次,所以INSTANCE也只加载一次。因为永远只有一个对象。不用加锁。

8.  完美中的完美- 万花筒写轮眼开!

public enum Mgr08 {  INSTANCE;  public void m() {}  public static void main(String[] args) {    for(int i=0;i<100;i++) {      new Thread(()->{        System.out.println(Mgr08.INSTANCE.hashCode());      }).start();    }  }}

不仅解决线程同步,还可以防止反序列化。不止心中有剑,手中也有剑~!

本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。

相关素材