Java设计模式-单例模式

单例模式,指的就是在整个软件系统中,采取一定的方法,让一个类只能存在一个实例对象,并且该类有且仅有一个提供实实力对象的静态方法。

什么时候需要用到单例模式

单例模式是一种最简单的设计模式,它主要的使用场景是哪些需要频繁创建和删除的对象,或者是哪些每次创建或者销毁都需要占用很多系统资源的“大”类,使用单例模式可以提升应用的性能。

实现单例模式的八种方式:

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全,同步方法)
  • 懒汉式(线程安全,同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举

饿汉式(静态常量)

实现饿汉式(静态常量)单例模式的步骤:

  1. 私有化构造器
  2. 提供私有的静态实例
  3. 对外提供一个public方法获得该对象的实例(getInstance)
public class HungryMan {
    private HungryMan(){ }

    private static final HungryMan instance = new HungryMan();

    public static HungryMan getInstance(){
        return instance;
    }
}

饿汉式(静态常量)实现的单例模式特别简单,也是一般最最常用的。他在类被加载的时候就已经创建好了类的对象的实例,所以是线程安全的。但是相对的问题就是可能会造成系统资源的浪费。

饿汉式(静态代码块)

实现和上面的饿汉式没有什么本质的区别,就是将new实例的步骤放到了静态代码块中执行。

public class HungryManStaticBlock {
    private HungryManStaticBlock() {
    }

    private static final HungryManStaticBlock instance;
   
    static {
        instance = new HungryManStaticBlock();
    }
    
    public static HungryManStaticBlock getInstance(){
        return HungryManStaticBlock.instance;
    }
}

懒汉式(线程不安全)

懒汉式实现的单例模式是通过在获取实例的时候判断实例时候为空的方式,来解决饿汉式实现的单利模式资源浪费的问题。因为如果这个对象加载后从始至终没有获取过实例,那么这个类的实例将永远不会被创建。但是由于是获取时创建的对象,在多线程的情况下有线程安全问题,不能保证单例。

public class LazyMan {
    private LazyMan(){
    }
    
    private static LazyMan instance;
    
    public static LazyMan getInstance(){
        if (instance == null) instance = new LazyMan();
        return instance;
    }
}

在实际开发中不能使用线程不安全的懒汉式。

懒汉式(同步方法&代码块)

public class SyncLazyMan {
    private SyncLazyMan() {
    }

    private static SyncLazyMan instance;

    public synchronized static SyncLazyMan getInstance() {
        if (SyncLazyMan.instance == null) {
            SyncLazyMan.instance = new SyncLazyMan();
        }
        return instance;
    }
}

实用同步方法的形式确实可以解决上面懒汉式的多线程问题,但是又会面临一个问题就是效率太低,因为每一个线程进入getInstance()方法的时候都会阻塞,等待前面的线程得到实例后进行判断,在实际开发中非常不推荐这种形式。

改用同步代码块

可能会有人想着,既然同步方法效率低,那我直接用同步代码块锁getInstnce()方法可不可行呢?

public class SyncLazyMan2 {
    private SyncLazyMan2() {
    }

    private static SyncLazyMan2 instance;

    public  static SyncLazyMan2 getInstance() {
      synchronized(SyncLazyMan2.class){
        if (SyncLazyMan2.instance == null) {
              SyncLazyMan2.instance = new SyncLazyMan2();
          }
      }
        return instance;
    }
}

这种方法肯定是不行的,这个同步代码并没有阻止线程进入if语句进判断,在没有解决线程安全的同时,又降低了效率,实际开发中不能使用这种方法。

双重检查

public class DoubleClick {
    private DoubleClick() {
    }

    private static volatile DoubleClick instance;

    public static DoubleClick getInstance() {
        if (instance == null) {
            synchronized ("double-click") {
                if (instance == null) {
                    instance = new DoubleClick();
                }
            }
        }
        return instance;
    }
}

volatile关键字是保证这个属性在多线程的情况下可以保证属性能够第一时间更新最新值。这种方式在保证了线程安全的同时实现了懒加载,同时效率还比较高,在实际开发中,如果有懒加载的需求,推荐使用此方式。

静态内部类

public class InnerStaticClass {
    private InnerStaticClass() {
    }

    private static class Inner {
        private static final InnerStaticClass instance = new InnerStaticClass();
    }

    public static InnerStaticClass getInstance() {
        return Inner.instance;
    }
}

使用静态内部类的方式实现的单例模式是推荐使用的方式,它利用JVM的类加载机制,在保证了线程安全的同时,实现了懒加载,效率还高

枚举

前面虽然有很多很好的实现单例模式模式的方式,但是他们都逃不过Java开的后门-反射。能够完全避免反射,同时线程安全的单例模式只有枚举实现的单例模式

public enum EnumSingleton {
    INSTANCE;
    
    public void aMethod(){
        System.out.println("巴拉巴拉");
    }
}

我们可以通过

EnumSingleton instance = EnumSingleton.INSTANCE;
instance.aMethod();

来获取实例和使用实例方法。

posted @ 2021-01-29 11:41  FarajMujey  阅读(78)  评论(0)    收藏  举报