Java设计模式之单利模式(Singleton)

单利模式的应用场景:spring

  单利模式(Singleton Pattern)是指确保一个类在任何状况下都绝对只有一个实例。并提供一个全局反访问点。单利模式是建立型模式。单利模式在生活中应用也很普遍,好比公司CEO只有一个,部门经理只有一个等。JAVA中ServletCOntext,ServetContextCOnfig等,还有spring中ApplicationContext应用上下文对象,SessionFactory,数据库链接池对象等。使用单利模式能够将其常驻于内存,能够节约更多资源。数据库

写法:安全

  1:懒汉模式(线程不安全)多线程

/**
 * 线程不安全的懒汉式单利模式
 * 
 * Created by gan on 2019/11/17 17:33.
 */
public class LazySingleton {
    private static LazySingleton instance;

    //构造方法私有化
    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (instance != null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

  上面的代码,提供一个静态对象instance,构造函数私有化防止外部建立对象,提供一个静态的getInstance方法来给访问者一个单利对象。这种写法的缺点就是没有考虑到线程安全问题,当多个访问者同时访问的时候颇有可能建立多个对象。之因此叫懒汉式,是由于这种写法是使用的时候才建立,起到了懒加载Lazy loading的做用,实际开发中不建议采用这种写法。并发

  2:线程安全的懒汉式(加锁)ide

/**
 * 线程安全的懒汉式单利模式
 * 
 * Created by gan on 2019/11/17 17:33.
 */
public class LazySingleton {
    private static LazySingleton instance;

    //构造方法私有化
    private LazySingleton() {
    }

    public synchronized static LazySingleton getInstance() {
        if (instance != null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

  这种写法就是在第一种的基础上添加了synchronized关键字保证了线程安全。这种写法在并发高的时候虽然保证了线程安全,可是效率很低,高并发的时候全部访问的线程都要排队等待,因此实际开发中也不建议采用。函数

  3:恶汉式(线程安全)高并发

/**
 * 饿汉式(线程安全)
 * Created by gan on 2019/10/28 22:52.
 */
public class HungrySigleton {

    public static final  HungrySigleton  instance = new HungrySigleton();

    private HungrySigleton(){}

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

  直接在运行(加载)这个类的时候建立了对象,以后直接访问。显然这种方式没有起到Lazy loading的效果。可是是线程安全的,实际开发中仍是比较经常使用。spa

  4:静态内部类(线程安全)线程

/**
 * 静态内部类方式
 * Created by gan on 2019/11/17 17:46.
 */
public class StaticInnerClassSingleton {

    //构造方法私有化
    private StaticInnerClassSingleton() {}

    //内部类
    private static class HolderInnerClass {
        //须要提供单利对象的外部类做为静态属性加载的时候就初始化
        private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }

    //对外暴漏访问点
    public static StaticInnerClassSingleton getInstance() {
        return HolderInnerClass.instance;
    }
}

  这种内部类跟饿汉式单利有不少类似的地方,相比饿汉式单利模式的区别也是好处在于:静态内部类不在单利类加载时就加载,而是在调用getInstance()方法的时候才进行加载,达到了相似于懒汉式的效果,并且这种方法又是线程安全的。实际开发中也建议采用。

  5:枚举方法单利(线程安全)

/**
 * 枚举单利模式
 * Created by gan on 2019/11/17 17:57.
 */
public enum EnumSingleton {
    INSTANCE;

    public void otherMetthod() {
        System.out.println("须要单利对象调用的方法。。。");
    }
}

  Effective Java做者Josh Bloch提倡的方式,好处有以下:

  1:自由串行化。

  2:保证了一个实例

  3:线程安全

  这种方式防止了单利模式被破坏,并且简洁写法简单,并且绝对的线程安全,可是有个缺点就是不能继承。

  6:双重检查法(一般线程安全,低几率不安全)

/**
 * Double check
 * Created by gan on 2019/11/17 18:03.
 */
public class DoubleCheckSingleton {
    private static DoubleCheckSingleton instance;

    private DoubleCheckSingleton() {}

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

  上面的这种写法在并发极高的时候也可能会出现问题(固然这种几率很是小,可是毕竟仍是有的嘛),解决的方案就是给instance的声明加上volatile关键字便可。因而就出现了下面第7总写法。

  7:Double check(volatile)

/**
 * Double check volatile
 * Created by gan on 2019/11/17 18:03.
 */
public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton instance;

    private DoubleCheckSingleton() {}

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

volatile关键字的其中一个做用就是禁止指令重排序,把instance声明volatile后,对它的操做就会有一个内存屏障(什么是内存屏障?),这样在赋值完成以前,就不会调用读操做。这里具体的缘由网上也是众说纷纭,这里不进行具体阐述。

  8:ThreadLocal实现单利模式(线程安全)

/**
 * ThreadLocal实现单利模式
 * Created by gan on 2019/11/17 18:17.
 */
public class ThreadLocalSingleton {

    private static final ThreadLocal<ThreadLocalSingleton> threadLocal = new ThreadLocal() {
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };

    private ThreadLocalSingleton(){}

    public static ThreadLocalSingleton getInstance(){
        return threadLocal.get();
    }
}

  ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程堆数据的访问冲突。对于多线程资源共享问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal则采用了“以空间换时间”的方式(主要就是避免了加锁排队)。 前者提供一份变量,让不一样的线程排队访问,然后者为每个线程提供了一份变量,所以能够同时访问而互不影响。可是实际是建立了多个单利对象的。

 

单利模式的破坏:

  1:序列化破坏

    一个对象建立好之后,有时候须要将对象序列化而后写入磁盘。下次在从磁盘中读取并反序列化,将其转化为内存对象。反序列化后的对象会从新分配内存,即建立型的对象。这样就违背了单利模式的初衷。解决这种方式的方法就是在单利类中新增一个 private Object readResolve();方法便可,具体缘由能够看看序列化和反序列化的源码。

  2:反射

    经过反射“暴力破解”也能破坏单利模式,具体暂时不阐述。

  3:克隆

    克隆也会破坏单利模式,具体暂时不阐述。

相关文章
相关标签/搜索