单例模式
单例模式是代码里只有一个实例,因此需要考虑如何防止别人再new一个实例。
思路一(饿汉式)
很简单,把构造函数写成private就可以,然后在类里直接构造一个private实例,只public一个get接口(getInstance)这样就只能获得实例,不能改写,也不能new一个新实例。
另外需要注意的是,创建实例和获得实例的函数都是static类型的,也就是只属于类本身。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package singleton;
public class Mgr01 { private static final Mgr01 INSTANCE = new Mgr01(); private Mgr01() {};
public static Mgr01 getInstance() {return INSTANCE;}
public static void main(String[] args){ Mgr01 m1 = Mgr01.getInstance(); Mgr01 m2 = Mgr01.getInstance(); System.out.println(m1 == m2); } }
|
另外这是线程安全的,因为jvm保证每一个class只漏到内存一次,因此只初始化一次(多线程访问也没关系)——推荐使用!
思路二
和饿汉式一样,只不过把构造函数改成静态语句块,本质上是一样的。
1 2 3 4
| private static final Mgr02 INSTANCE; static { INSTANCE = new Merge02{}; }
|
思路三(懒汉式)
和饿汉式的区别是什么时候用什么时候初始化。这种方法是调用getInstance的时候才进行初始化(需要加个判断操作),而饿汉式是只要类加载那么就自动初始化。但是也带来了线程不安全的问题,因为有可能多个线程同时调用getInstance函数,在进行if判断的时候为null,然后建立了多个实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package singleton;
public class Mgr03 { private static Mgr03 INSTANCE; private Mgr03() {};
public static Mgr03 getInstance() { if(INSTANCE == null){ INSTANCE = new Mgr03(); } return INSTANCE; }
public static void main(String[] args){ Mgr03 m1 = Mgr03.getInstance(); Mgr03 m2 = Mgr03.getInstance(); System.out.println(m1 == m2); } }
|
思路四
对于懒汉式如何解决线程安全问题,那么就加个锁,但是相应的也会导致性能的下降。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package singleton;
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 static void main(String[] args){ for(int i=0;i<100;i++){ new Thread(()->{ System.out.println(Mgr04.getInstance().hashCode()); }).start(); } } }
|
思路五:静态内部类方式
jvm保证单例,因此class只会加载一次,MgrHolder也只加载一次,因此保证了线程安全。
加载外部类的时候不会加载静态内部类,保证了懒加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package singleton;
public class Mrg05 { private Mrg05() { } private static class Mrg05Holder { private final static Mrg05 Instance = new Mrg05(); } public static Mrg05 getInstance() { return Mrg05Holder.Instance; }
public static void main(String[] args) { for(int i=0;i<100;i++){ new Thread(()->{ System.out.println(Mgr04.getInstance().hashCode()); }).start(); } } }
|
思路六
枚举单例,非常鬼才的一种方式,不仅可以解决线程同步,同时可以防止反序列化,因为枚举类没有构造方法。
1 2 3 4 5 6 7 8 9 10 11 12 13
| package singleton;
public class Mrg06 { INSTANCE;
public static void main(String[] args) { for(int i=0;i<100;i++){ new Thread(()->{ System.out.println(Mgr06.INSTANCE.hashCode()); }).start(); } } }
|
除了以上六种,还有在第四种的基础上扩展的两种方法,为了增加效率,减少同步代码块的大小,但是需要额外判断两次instance == null,代码略。这里有一个重点就是需要加上volatile,如果不加volatile可能因为指令重排导致一些问题。
总结:比较常用且完美的方法就是饿汉式(常用简洁)、懒汉式两次判断+静态内部类。枚举单例很完美但是不常用。