目录

饿汉式(Eager Singleton)

懒汉式(Lazy Singleton)

懒汉式+synchronized

Double Check Lock(DCL)

Holder方式

枚举方式

Holder+枚举方式


饿汉式(Eager Singleton)

package main.singleton;

/**
 * Created by leboop on 2018/11/27.
 */
public class EagerSingleton {
    // static:getInstance方法是静态方法,只能调用静态变量
    // final:不允许类的内部对该实例进行修改,保证单例
    // getInstance未调用前,类加载的时候instance已经被实例化,实例化的有点着急,所以有些地方称之为“饿汉式”
    private final static EagerSingleton INSTANCE = new EagerSingleton();

    //private:私有化构造方法,不允许类的外部使用new创建实例
    private EagerSingleton() {
    }

    //static声明方法的原因:外部无法创建类的实例,只能通过类来调用方法
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}


/**
 * 主函数入口,测试单例
 */
class SingletonMain {
    public static void main(String[] args) {
        EagerSingleton singleton1 = EagerSingleton.getInstance();
        EagerSingleton singleton2 = EagerSingleton.getInstance();
        //输出true
        System.out.println(singleton1 == singleton2);
    }
}

单例构造步骤:

1、构造方法EagerSingleton()私有化

这一步是保证单例的基础,禁止外部通过new方式创建多个实例。

2、static修饰getInstance成员方法

构造方法已经私有化,外部不可以通过构造方法创建实例对象,只能通过EagerSingleton.getInstance的方式获取对象,所以getInstance成员方法需要声明为static。

3、使用static和final修饰INSTANCE

getInstance方法是static方法,只能访问static成员变量,所以INSTANCE需要使用static修饰。final也是保证单例的基础,禁止内部其他地方修改INSTANCE实例。

4、维度分析

(1)线程安全性

饿汉式单例模式是线程安全的。INSTANCE作为类变量在初始化的过程中会被收集进<clinit>()方法中,该方法能够保证同步,所以INSTANCE在多线程的情况下只能被实例化一次。

(2)性能

如果EagerSingleton中成员变量比较少,且占用的内存资源不多,该方式也未尝不可;但是,如果EagerSingleton中成员变量占用资源较多,那么该方式会有些不妥。instance被ClassLoader加载后可能很长时间才被使用,意味着instance实例开辟的堆内存空间会驻留更久的时间。

(3)懒加载

该方式无法进行懒加载。

 

懒汉式(Lazy Singleton)

        在饿汉式单例模式中,未调用getInstance方法就已经实例化了INSTANCE实例,有些时候我们并不需要实例,而只是通过类来调用其他静态方法,事先创建INSTANCE实例就有些多余,所以希望在调用getInstance方法时才创建才好,这就是懒汉式单例模式,代码如下:

package main.singleton;

/**
 * Created by leboop on 2018/11/27.
 * final修饰:不可被其他类继承
 */
public class LazySingleton {
    //定义实例,不直接初始化
    //不能使用final修饰,也带来了一定的隐患,类的内部其他地方可以修改该实例
    private  static LazySingleton instance = null;

    //构造方法私有化:不允许外部new实例化
    private LazySingleton() {
    }

    //方法中实例化,没被实例化时才实例化
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }

        return instance;
    }
}

class LazySingletonMain {
    public static void main(String[] args) {
        LazySingleton singleton1 = LazySingleton.getInstance();
        LazySingleton singleton2 = LazySingleton.getInstance();
        //输出true
        System.out.println(singleton1 == singleton2);
    }
}

单例构造步骤:

1、构造方法私有化

和饿汉式单例模式构造相同。

2、static修饰getInstance成员方法

和饿汉式单例模式构造相同。

3、static修饰instance

和饿汉式单例模式构造相同。不能使用final修饰,这也给类的内部修改该实例带来隐患。

4、维度分析

(1)线程安全性

        该方式是线程不安全的。类变量instance=null,因此当LazySingleton.class被初始化的时候instance并不会被实例化,会在getInstance中判断是否为空后再决定是否需要实例化。现在我们假设有两个线程:t1和t2,当t1调用getInstance()方法时,看到instance==null,未被实例化,那么t1将会执行该方法中的instance=new LazySingleton()进行初始化,但是假设t1执行到该方法前,t2线程也调用了getInstance方法,读到instance=null,因为此时实例尚未实例化,t2会进入if语句执行instance=new LazySingleton(),此次会创建两个instance实例,每个线程拥有一个。我们测试一下:

package main.singleton;


import java.util.HashSet;
import java.util.Set;

/**
 * Created by leboop on 2018/11/27.
 */
public class LazySingletonTest {
    //存放单例
    private static Set<LazySingleton> set = new HashSet<>();

    public static void main(String[] args) {
        while (set.size() < 2) {
            // 清空
            set.clear();
            //线程t1
            new Thread(() -> {
                LazySingleton singleton = LazySingleton.getInstance();
                synchronized (set) {
                    set.add(singleton);
                }
            }
                    , "t1").start();

            // 线程t2
            new Thread(() -> {
                LazySingleton singleton = LazySingleton.getInstance();
                synchronized (set) {
                    set.add(singleton);
                }
            }, "t2").start();

        }

        System.out.println("set的大小:" + set.size());
        for (LazySingleton singleton : set) {
            System.out.println(singleton);
        }


    }
}

运行结果:

set的大小:2
main.singleton.LazySingleton@616831d4
main.singleton.LazySingleton@4ff782ab

 (2)性能

性能比饿汉式高。不能保证单例,性能已经无从谈起。

(3)懒加载

该方式是懒加载。

 

懒汉式+synchronized

        前面我们已经知道懒汉式是线程不安全的,下面通过添加synchronized使其变为线程安全的,代码如下:

package main.singleton;

/**
 * Created by leboop on 2018/11/27.
 */
public final class SynchronizedLazySingleton {
    //构造方法私有化:不允许外部new实例化
    private SynchronizedLazySingleton() {
    }

    //定义实例,不直接初始化
    private static SynchronizedLazySingleton singleton = null;

    //方法中实例化
    public static synchronized SynchronizedLazySingleton getInstance() {
        if (singleton == null) {
            singleton = new SynchronizedLazySingleton();
        }
        return singleton;
    }
}

我们直接在懒汉式的getInstance方法上添加了同步关键字synchronized。

维度分析:

(1)线程安全性

该方式因添加了synchronized关键字保证了线程安全

(2)懒加载

该方式是懒加载

(2)性能

synchronized关键字天地的排他性导致了getInstance方法只能在同时时候被一个线程访问,性能低下。

 

Double Check Lock(DCL)

        在《懒汉式+synchronized》中,直接在getInstance方法前通过暴力方式添加同步关键字synchronized使其线程安全,但是这种方法简单粗暴,如果该方法在实例化前还有很多代码逻辑需要处理,效率极其低下。所以我们可以细化synchronized锁,代码如下:

package main.singleton;

/**
 * Created by leboop on 2018/11/27.
 */
public class RefineSynchronizedLazySingleton {
    //构造方法私有化:不允许外部new实例化
    private RefineSynchronizedLazySingleton() {
    }

    //定义实例,不直接初始化
    private static volatile RefineSynchronizedLazySingleton singleton = null;

    //方法中实例化
    public static RefineSynchronizedLazySingleton getInstance() {
        if (singleton == null) {
            synchronized (RefineSynchronizedLazySingleton.class) {
                if (singleton == null) {
                    singleton = new RefineSynchronizedLazySingleton();
                }
            }
        }

        return singleton;
    }
}

这种方式在创建实例前,进行了两次检查(if ( singleton==null )),称之为double check lock,简称为DCL。该方式注意使用了volatile关键字。如果不使用不能保证单例。尚未找到例子证明。现只有原理说明。假设单例中还有一个私有成员变量a,定义如下:

private int a=1;

 new关键字实例化时,指令有如下几个:

指令1:给准备实例化的实例分配一块内存 M,a先初始化为0;

指令2:在内存 M 上对成员变量赋值,此时a赋值为1;

指令3:然后 M 的地址赋值给 instance 变量。

但是实际上,经过编译器优化后可能会出现指令2和指令3重排序:

指令1:分配一块内存 M,a先初始化为0;

指令3:然后 M 的地址赋值给 instance 变量。

指令2:在内存 M 上对成员变量赋值,此时a赋值为1;

假设现在线程A执行完指令3,那么singleton对象指向的实例的成员变量a的值是0,此时线程B进行第一次check,发现singleton已经指向实例,不为空,所以直接返回。那么此时B线程拿到的是未完全实例化好的单例,a的值为0,接着线程A执行指令3完成实例化。如图:

Java设计模式(一)之单例模式(Singleton)_Java

引入volatile后,可以禁止有关singleton的指令重排序。

 

Holder方式

        Holder方式是使用内部类的方式创建单例,代码如下:

package main.singleton;

/**
 * Created by leboop on 2018/11/27.
 */
public final class HolderSingleton {
    
    private HolderSingleton(){}
    
    private static class Holder{
        private static HolderSingleton holderSingleton=new HolderSingleton();
    }
    
    private static HolderSingleton getInstance(){
        return Holder.holderSingleton;
    }
}

该方式是一个线程安全,性能高,懒加载的方式,也是目前使用比较广的设计之一。

 

枚举方式

package main.singleton;

/**
 * Created by leboop on 2018/11/27.
 */
public enum EnumSingleton {
    SINGLETON;

    EnumSingleton() {
        System.out.println("SINGLETON将被初始化");
    }

    public static void method() {
        System.out.println("method的方法被调用");
    }

    public static EnumSingleton getInstance() {
        return SINGLETON;
    }
}

三维分析:

(1)线程安全

该方式是线程安全的。

(2)性能

性能比较只是相对的,推荐使用该方式。

(3)懒加载

无法懒加载

注:当调用method()方法时,会主动初始化。

 

Holder+枚举方式

package main.singleton;

/**
 * Created by leboop on 2018/11/27.
 */
public class HolderEnumSingleton {
    //构造方法私有化
    private HolderEnumSingleton() {
    }
    //使用enum枚举充当Holder
    private enum EnumHolder {
        INSTANCE;
        private HolderEnumSingleton instance;

        EnumHolder() {
            this.instance = new HolderEnumSingleton();
        }

        private HolderEnumSingleton getSingleton() {
            return instance;
        }
    }
    //获取单例
    public static HolderEnumSingleton getInstance() {
        return EnumHolder.INSTANCE.getSingleton();
    }
}

三维分析:

(1)线程安全

该方式是线程安全的

(2)性能

推荐使用。

(3)懒加载

该方式是懒加载的。

 

以上创建方式各个各的有点,需要根据实际情况选用,比如是否考虑线程安全,是否考虑性能等。