设计模式之单例模式

 

Java设计模式之单例模式详解

 

来来回回学习设计模式有一段时间了,总是一知半解是是而非,正好趁现在工作不忙,深入研究下设计模式,先从最简单的单例模式入手。

一、单例模式定义:

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。

二、单例模式特点:

  1、单例类只能有一个实例。

  2、单例类必须自己创建自己的唯一实例。

       3、单例类必须给所有其他对象提供这一实例。

单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。

从具体实现角度来说:

  1、是单例模式的类只提供私有的构造函数,

  2、是类定义中含有一个该类的静态私有对象,

  3、是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。

三、实现单例模式的方式

1.饿汉式单例(立即加载方式)

//饿汉式单例模式
public class Singleton {

    public static String staticString = "Test";

    //私有构造函数防止外部创建对象
    private Singleton(){
        System.out.println("对象初始化");
    }

    //静态对象对象初始化
    private static Singleton singleton = new Singleton();

    //静态工程方法
    public static Singleton getInstance(){
        return singleton;
    }

    public void doSomething(){
        System.out.println(this.getClass().getName());
    }
}

饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,由于是jvm在加载类的机制决定了其是线程安全的。

2.懒汉式单例(延迟加载方式,线程不安全)

//懒汉式单例(延时加载)有多线程访问问题
public class Singleton2 {
    //私有构造函数
    private Singleton2(){
    }

    //静态私有成员变量,未初始化
    private static Singleton2 singleton2 = null;

    //静态方法中检查实例是否未创建
    public static Singleton2 getInstance(){
        if(singleton2 == null){
            singleton2 = new Singleton2();
        }
        return  singleton2;
    }

    public void doSometing(){
        System.out.println(this.getClass().getName());
    }
}

该示例虽然用延迟加载方式实现了懒汉式单例,但在多线程环境下会产生多个single对象,如何改造请看以下方式:

使用synchronized同步锁

//检查一次
public class Singleton3 {

    private Singleton3(){}

    private static Singleton3 singleton3 = null;

    public static synchronized Singleton3 getSingleton3(){
            if (singleton3 == null)
                singleton3 = new Singleton3();
        return singleton3;
    }

    public void doSomething(){
        System.out.println(this.getClass().getName());
    }
}

 

在方法上加synchronized同步锁或是用同步代码块对类加同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。

//DCL机制

//双重检查
public class Singleton4 {

    private static Singleton4 singleton4 = null;

    private Singleton4(){}

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

    public void doSomething(){
        System.out.println(this.getClass().getName());
    }

}

使用双重检查进一步做了优化,可以避免整个方法被锁,只对需要锁的代码部分加锁,可以提高执行效率。

3.静态内部类实现

//静态内部类
public class Singleton5 {
    private Singleton5(){
        if(InnerObjcet.singleton5 != null){
            throw new IllegalStateException();
        }
    }
    private static class InnerObjcet{
        private static Singleton5 singleton5 = new Singleton5();

        static {
            System.out.println("内部类被加载");
        }
    }
    public static  Singleton5 getInstance(){
       return InnerObjcet.singleton5;
    }
    public void doSomething(){
        System.out.println(this.getClass().getName());
    }
}

 

静态内部类通过jvm类加载机制保证了单例在多线程并发下的线程安全性。

4.static静态代码块实现 

//静态代码块
public class Singleton6 {
    private Singleton6(){}
    private static Singleton6 singleton6 = null;
    static {
        singleton6 = new Singleton6();
    }

    public static Singleton6 getInstance(){
        return singleton6;
    }

    public void doSomething(){
        System.out.println(this.getClass().getName());
    }
}

本质上与1是一样的。

5.枚举实现

//枚举实现单例
public enum  Singleton7 {
    INSTANCE;
    public static  Singleton7 getInstance(){
        return INSTANCE;
    }
    public void doSometing(){
        System.out.println(this.getClass().getName());
    }
}

也可以将枚举放入类的内部。

以上是几种常见的单例实现方式。在实际开发中需要考虑以下几个方面:

反射能否打破单例?

  首先,对外部类的私有构造器中加入 instance==null 的判断,防止反射入侵外部类。

  其次,静态内部类保证了从外部很难获取 SingletonHolder 的 Class 对象,从而保证了内部类不会被反射。

多线程能否打破单例?

  Holder 模式借用了饿汉模式的优势,就是在加载类(内部类)的同时对 instance 对象进行初始化。

  由于自始至终类只会加载一次,所以即使在多线程的情况下,也能够保持单例的性质。

 优势?劣势?

  优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。

  劣势:需要多加载一个类;相比于懒汉模式,Holder 创建的单例,只能通过 JVM 去控制器生命周期,不能手动 destroy。

此外还要考虑代码是否简单明了,效率等方面。

 

 

 

posted on 2019-07-16 09:04  大-狗  阅读(138)  评论(0)    收藏  举报