文章目录

Java单例模式

单例模式是Java中最简略的设计模式之一。这种类型的设计模式属于创立型模式,它提供了一种创立 对象的最佳形式

单例模式确保在一个应用程序中某一个类只有一个实例,而且自行实例化并向整个零碎提供这个实例 单例实例。

满足条件

单例模式只应在有真正的“繁多实例”的需要时才可应用:

  1. 单例类只能有一个实例
  2. 单例类必须本人创立本人的惟一实例
  3. 单例类必须给所有其余对象提供这一实例

两种模式

Java中实现单例模式能够通过两种模式实现:

  • 懒汉模式 (类加载时不初始化)
  • 饿汉模式 (在类加载时就实现了初始化,所以类加载较慢,但获取对象的速度快)

设计要求

编写单例必须满足上面的条件:

  1. 构造方法变成公有
  2. 提供一个静态方法获取单实例对象

饿汉模式

饿汉模式基于classloader机制防止了多线程的同步问题(动态初始化将保障在任何线程可能拜访到域之前初始化它),不过,instance在类装载时就实例化,这时候初始化instance显然没有达到懒加载(lazy loading)的成果

饿汉单例绝对比拟容易了解,个别体现为以下两种模式:

package com.shixun.design.singleton;public class  Singleton1  {     private static Singleton1 instance = new Singleton1();    // 公有构造方法,保障外界无奈间接实例化。     private  Singleton1()  {     }    // 通过私有的静态方法获取对象实例     public  static  Singleton1  getInstance()  {         return instance;    }}

也能够将动态对象初始化放在动态代码块中

package com.shixun.design.singleton;public class  Singleton2  {     private static Singleton2 instance = null;    // 对象初始化放在动态代码块中    static {         instance = new Singleton2();    }    // 公有构造方法,保障外界无奈间接实例化。    private  Singleton2()  {     }    // 通过私有的静态方法获取对象实例    public  static  Singleton2  getInstance()  {         return instance;    }}

懒汉形式

实现单例模式可能进步类加载性能,然而和饿汉模式借助与JVM的类加载外部同步机制实现了线程平安不同,须要在提早加载时留神单例实例的线程安全性,如果简略粗犷的实现,在多线程环境中将引起运行异样。

例如上面代码将引起运行异样:

package com.shixun.design.singleton;public class  Singleton3  {     private static Singleton3 instance;    private  Singleton3()  {     }    public  static  Singleton3  getInstance()  {         if (instance == null) {             instance = new Singleton3();        }        return instance;    }}

上述代码多线程同时拜访时可能会产生多个示例,甚至会毁坏实例,违反单例的设计准则

用上面代码也能测试进去:

package com.shixun.design.singleton;public class  SingletonTest  implements  Runnable{     @Override    public  void  run()  {         Singleton3 singleton3 =Singleton3.getInstance();        System.out.println(singleton3);    }    public  static  void  main(String[] args)  {         for (int i=0;i<10;i++){             SingletonTest myThread = new SingletonTest();            Thread thread = new Thread(myThread, String.valueOf(i));            Thread thread2 = new Thread(myThread, String.valueOf(i));            Thread thread3 = new Thread(myThread, String.valueOf(i));            thread.start();            thread2.start();            thread3.start();        }    }}

懒汉式多线程解决方案

synchronized

能够为返回单例实例的办法设置同步用来保障线程安全性

package com.shixun.design.singleton;public class  Singleton4  {     private static Singleton4 instance;    private  Singleton4()  {     }    public  synchronized  static  Singleton4  getInstance()  {         if (instance == null) {             instance = new Singleton4();        }        return instance;    }}

这种写法可能在多线程中很好的工作,而且看起来它也具备很好的懒加载(lazy loading),但遗憾的是,因为整个办法被同步,因而效率绝对较低

双查看锁形式

应用双查看锁须要进行两次instance == null的判断

  • 第一次判断没有锁,如果install不为null间接返回单实例对象,提高效率
  • 第二次判断避免多线程创立多个实例,如果A和B 两个线程同时争抢synchronized锁,A先争抢到锁,B 期待,A线程instance赋值实例化对象,开释锁,B线程获取到到锁,如果没有第二次判断的话,间接又会创建对象,那么就不合乎单例要求

并且还须要为这个动态对象加上volatile关键字, volatile在这里的作用是:告诉其余线程及时更新变量;保障有序性,禁止指令重排序。

告诉其余线程及时更新变量还简单明了,第二个作用是这样的,咱们举例说明一下:

起因举例说明: 在执行instance = new Singleton()语句时,一共是有三步操作的。

  1. 堆中分配内存
  2. 调用构造方法进行初始化
  3. 将instance援用指向内存地址。

在这三步有可能会产生指令重排序即有两种后果可能产生: 123与132(不管怎么重排序,单线程程序 的执行后果不会扭转)

如果A线程执行到 instance = new Singleton() ,此时2 ,3产生重排序,选执行3,则instance曾经不为 null,然而指向的对象还未初始化实现,如果此时B对象判断instance 不为null就会间接返回一个未初始 化实现的对象。

双查看锁形式代码如下:

package com.shixun.design.singleton;public class  Singleton5  {     private volatile static Singleton5 instance;    private  Singleton5()  {     }    // 应用双查看锁形式    public  static  Singleton5  getInstance()  {         if (instance == null) {             synchronized(Singleton5.class){                 if(instance == null){                     instance = new Singleton5();                }            }        }        return instance;    }}

再测一下,没有问题:

package com.shixun.design.singleton;public class  SingletonTest  implements  Runnable{     @Override    public void run() {         Singleton5 singleton5 =Singleton5.getInstance();        System.out.println(singleton5);    }    public static void main(String[] args) {         for (int i=0;i<10;i++){             SingletonTest myThread = new SingletonTest();            Thread thread = new Thread(myThread, String.valueOf(i));            Thread thread2 = new Thread(myThread, String.valueOf(i));            Thread thread3 = new Thread(myThread, String.valueOf(i));            Thread thread4 = new Thread(myThread, String.valueOf(i));            Thread thread5 = new Thread(myThread, String.valueOf(i));            thread.start();            thread2.start();            thread3.start();            thread4.start();            thread5.start();        }    }}

动态外部类

之前提到了,动态初始化将在实例被任何线程拜访到之前对其进行初始化,因而,能够借助于这个个性对懒汉单例进行革新:

动态外部类加载机制:应用时候才被加载,而且多线程状况下, classloader可能保障只加载一份字节码

代码如下:

package com.shixun.design.singleton;public class  Singleton6  {     private static class  SingletonHolder  {         private final static Singleton6 Instance = new Singleton6();    }    private  Singleton6()  {     }    public  static  final  Singleton6  getInstance()  {         return SingletonHolder.Instance;    }    public  void  say()  {         System.out.println("调用了say办法");    }    public  static  void  sayHello()  {         System.out.println("调用了sayHello办法");    }

测试也OK:

package com.shixun.design.singleton;public class  SingletonTest  implements  Runnable{     @Override    public void run() {         Singleton6 singleton6 =Singleton6.getInstance();        System.out.println(singleton6);        singleton6.say();    }    public static void main(String[] args) {         for (int i=0;i<10;i++){             SingletonTest myThread = new SingletonTest();            Thread thread = new Thread(myThread, String.valueOf(i));            Thread thread2 = new Thread(myThread, String.valueOf(i));            Thread thread3 = new Thread(myThread, String.valueOf(i));            Thread thread4 = new Thread(myThread, String.valueOf(i));            Thread thread5 = new Thread(myThread, String.valueOf(i));            thread.start();            thread2.start();            thread3.start();            thread4.start();            thread5.start();        }    }}

枚举(别瞎用)

JDK1.5之后引入了枚举,因为枚举的个性,能够利用其来实现单例,它不仅能防止多线程同步问 题,而且还能避免反序列化从新创立新的对象(序列化和反序列化后是同一个对象)

代码如下

package com.shixun.design.singleton;public enum  Singleton7 {     INSTANCE;    public  void  say(){         System.out.println("say ni hello!");    }}

测试一下:

package com.shixun.design.singleton;public class  SingletonTest  implements  Runnable{     @Override    public void run() {         Singleton7 singleton7 = Singleton7.INSTANCE;        System.out.println(singleton7.hashCode());        singleton7.say();    }    public static void main(String[] args) {         for (int i=0;i<10;i++){             SingletonTest myThread = new SingletonTest();            Thread thread = new Thread(myThread, String.valueOf(i));            Thread thread2 = new Thread(myThread, String.valueOf(i));            Thread thread3 = new Thread(myThread, String.valueOf(i));            Thread thread4 = new Thread(myThread, String.valueOf(i));            Thread thread5 = new Thread(myThread, String.valueOf(i));            thread.start();            thread2.start();            thread3.start();            thread4.start();            thread5.start();        }    }    /** * 生写懒汉式多线程问题 */    private static void method01() {         for (int i=0;i<100;i++){             SingletonTest myThread = new SingletonTest();            Thread thread = new Thread(myThread, String.valueOf(i));            Thread thread2 = new Thread(myThread, String.valueOf(i));            Thread thread3 = new Thread(myThread, String.valueOf(i));            thread.start();            thread2.start();            thread3.start();        }    }}