单例模式:Java 中最简单的设计模式之一。这种类型的设计模式属于建立型模式,它提供了一种建立对象的最佳方式。设计模式
特色: 缓存
优势:安全
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。多线程
描述:比较经常使用,但容易产生垃圾对象。
优势:JVM保证单例,不须要加锁就能保证线程安全,执行效率会提升。
缺点:在类加载时就进行实例化对象,浪费内存。并发
1 public class SingletonPatternDemo1 {
2
3 private static SingletonPatternDemo1 singleton = new SingletonPatternDemo1(); 4 5 private SingletonPatternDemo1(){ 6 7 } 8 9 public static SingletonPatternDemo1 getInstance(){ 10 return singleton; 11 } 12 13 }
写法一和写法二本质是相同的,都是饿汉式的实现方式,只是代码实现不一样。spa
1 public class SingletonPatternDemo2 {
2 private static final SingletonPatternDemo2 INSTANCE; 3 4 static{ 5 INSTANCE = new SingletonPatternDemo2(); 6 } 7 8 private SingletonPatternDemo2(){ 9 10 } 11 12 public static SingletonPatternDemo2 getInstance(){ 13 return INSTANCE; 14 } 15 }
描述:针对饿汉式在类加载时就进行对象实例化,浪费内存的缺点进行改进,使在加载类时只是申明惟一的内对象,并不进行对象实例化;而在第一次使用时进行对象的实例化。线程
优势:达到了在须要时初始化的目的。设计
缺点:多线程访问时会存在问题,不能保证线程安全code
1 public class SingletonPatternDemo3 {
2 private static SingletonPatternDemo3 INSTANCE; 3 4 private SingletonPatternDemo3(){ 5 6 } 7 8 public static SingletonPatternDemo3 getInstance(){ 9 if (INSTANCE == null){ 10 INSTANCE = new SingletonPatternDemo3(); 11 } 12 return INSTANCE; 13 } 14 15 public static void main(String[] args){ 16 for (int i = 0 ; i < 100 ; i++){ 17 new Thread(() -> { 18 System.out.println(SingletonPatternDemo3.getInstance().hashCode()); 19 }).start(); 20 } 21 } 22 }
/*这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。由于没有加锁 synchronized,因此严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工做。*/
描述:对于写法一虽然达到了使用时初始化可是线程不安全的特色,于是催生出了写法二使用synchronized加锁来保证线程安全。对象
优势:达到了在须要时初始化的目的,解决了多线程不安全的问题。
缺点:使用了synchronized加锁的方式来解决线程安全性问题,尤为是在class中static方法上加锁,极大地下降了代码的效率。
1 public class SingletonPatternDemo4 {
2
3 private static SingletonPatternDemo4 INSTANCE; 4 5 private SingletonPatternDemo4(){ 6 7 } 8 9 public static synchronized SingletonPatternDemo4 getInstance(){ 10 if (INSTANCE == null){ 11 INSTANCE = new SingletonPatternDemo4(); 12 } 13 return INSTANCE; 14 } 15 16 public static void main(String[] args){ 17 for (int i = 0 ; i < 100 ; i++){ 18 new Thread(() -> { 19 System.out.println(SingletonPatternDemo4.getInstance().hashCode()); 20 }).start(); 21 } 22 } 23 24 }
描述:在写法二中经过加锁的方式解决了多线程安全性的问题,但同时因为加锁位置致使代码效率极低;于是改变加锁的位置是否就能知足线程安全又能挽救一部分代码效率呢?大胆的作出假设是否再也不静态类方法上进行加锁,改在须要保障线程安全的代码块上加锁,是否是就能既能保障线程安全也能提供代码效率,催生出第三种在实例化对象的代码块上进行加锁。
优势:达到了在须要时初始化的目的,必定程度上提升了代码效率。
缺点:事实证实并不能保障线程安全。
1 public class SingletonPatternDemo5 {
2 private static SingletonPatternDemo5 INSTANCE; 3 4 private SingletonPatternDemo5(){ 5 6 } 7 8 public static SingletonPatternDemo5 getInstance(){ 9 if (INSTANCE == null){ 10 //妄图经过减小同步代码块的方式提升效率,然而不可行 11 synchronized(SingletonPatternDemo5.class) { 12 INSTANCE = new SingletonPatternDemo5(); 13 } 14 } 15 return INSTANCE; 16 } 17 18 public static void main(String[] args){ 19 for (int i = 0 ; i < 100 ; i++){ 20 new Thread(() -> { 21 System.out.println(SingletonPatternDemo5.getInstance().hashCode()); 22 }).start(); 23 } 24 } 25 }
经过分析代码:发现存在问题是由于当线程A和线程B并发执行,先是线程A执行到if(INSTANCE == null){}判断并知足条件,此时线程B也执行到此判断,可是线程A正在申请或刚刚申请到锁尚未执行到实例化对象,那么线程B的判断也是true,并进入等待锁的释放,于是线程A和B都会执行实例化对象,就会产生两个对象。因此这种写法不能保证线程安全。
也行存在这样的疑或,为何不把判断放进synchronized代码块中,这样就能保证线程安全。这种写法与写法二并无本质的不一样。
描述:经过写法三以及写法三的分析,在synchronized加锁先后都进行判断就能知足要求,于是诞生了双重判断的写法。
优势:达到了在须要时初始化的目的,必定程度上提升了代码效率,线程安全。
缺点:使用了synchronized加锁就有效率的损失,并且代码也愈来愈复杂。
1 public class SingletonPatternDemo6 {
2
3 private static volatile SingletonPatternDemo6 INSTANCE;//加上volatile是由于须要解决在进行JIT设置时存在的语句重排问题
4
5 private SingletonPatternDemo6(){ 6 7 } 8 9 public static SingletonPatternDemo6 getInstance() { 10 if (INSTANCE == null) {//第一个判断有必要吗?有必要,当一些线程判断到INSTANCE!=null时就返回了,不用都去竞争这把锁,提升代码效率 11 //双重判断 12 synchronized (SingletonPatternDemo6.class){ 13 if (INSTANCE == null) { 14 INSTANCE = new SingletonPatternDemo6(); 15 } 16 } 17 } 18 return INSTANCE; 19 } 20 21 public static void main(String[] args){ 22 for (int i = 0 ; i < 100 ; i++){ 23 new Thread(() -> { 24 System.out.println(SingletonPatternDemo6.getInstance().hashCode()); 25 }).start(); 26 } 27 } 28 29 }
描述:这种方式能达到双检锁方式同样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的状况,双检锁方式可在实例域须要延迟初始化时使用。
优势:JVM保证单例,不存在多线程不安全问题,加载外部类的时候不会加载内部类,能够实现懒加载,可以在使用时才进行实例化。
1 public class SingletonPatternDemo7 {
2
3 public SingletonPatternDemo7(){ 4 5 } 6 7 private static class SingletonPatternDemo7Holder{ 8 private static final SingletonPatternDemo7 INSTANCE = new SingletonPatternDemo7(); 9 } 10 11 public static SingletonPatternDemo7 getInstance() { 12 return SingletonPatternDemo7Holder.INSTANCE; 13 } 14 15 public static void main(String[] args){ 16 for (int i = 0 ; i < 100 ; i++){ 17 new Thread(() -> { 18 System.out.println(SingletonPatternDemo7.getInstance().hashCode()); 19 }).start(); 20 } 21 } 22 }
描述:这种方式是JAVA创始人之一, 《Effective Java》的做者 Josh Bloch 提倡的方式,它不只能避免多线程同步问题,并且还自动支持序列化机制,防止反序列化从新建立新的对象,绝对防止屡次实例化,但在实际中基本不用,由于这须要把class类改成enum类。
优势:不只保证了多线程的问题,还能防止反序列化。
1 public enum SingletonPatternDemo8 {
2
3 INSTANCE; 4 5 public static void main(String[] args){ 6 for (int i = 0 ; i < 100 ; i++){ 7 new Thread(() -> { 8 System.out.println(SingletonPatternDemo8.INSTANCE.hashCode()); 9 }).start(); 10 } 11 } 12 }
写在最后:通常状况下,使用饿汉式-写法一就能够了。只有在要明确实现 lazy loading 效果时,才会使用(三)登记式 / 静态内部类实现的方式。若是涉及到反序列化建立对象时,能够尝试使用(四)枚举实现的方式。若是有其余特殊的需求,能够考虑使用 懒汉式-写法四(双检锁/双重校验锁(DCL,即 double-checked locking))的方式。