java设计模式1——单例模式

java设计模式1——单例模式

一、单例模式介绍

1.一、核心做用:保证一个类只有一个实例,而且提供一个访问该实例的全局访问点

1.二、常见场景

1.三、单例模式的优势

1.四、常见的五种单例模式实现方式

二、饿汉式

2.一、第一步:私有化构造器。(防止外部直接new对象)

//保证类只有一个实例,私有其构造器
private SingletonDemo01() {

}

2.二、第二步:建立自身对象。

//建立自身对象
private static SingletonDemo01 instance = new SingletonDemo01();

2.三、第三步:提够对外全局公开的方法

//全局公开的方法
public static SingletonDemo01 getInstance() {
    return instance;
}

2.四、测试是否为单例

class SingletonDemo01Test {
    public static void main(String[] args) {
        SingletonDemo01 instance = SingletonDemo01.getInstance();
        SingletonDemo01 instance2 = SingletonDemo01.getInstance();

        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        System.out.println(instance == instance2);
    }
}

输出的结果为:java

356573597
356573597
true

2.五、弊端分析:

饿汉式一上来就会对对象进行建立,无论后续有没有用到,若是对于较大内存的对象然后续也都没有用到,则会形成较大的内存空间的浪费。

2.六、本类所有代码

package com.xgp.company.第一种_单例模式.饿汉式;

/**
 *
 * 核心:保证一个类只有一个实例,而且提供一个范围该实例的全局访问点
 */
public class SingletonDemo01 {

    //保证类只有一个实例,私有其构造器
    private SingletonDemo01() {

    }

    //建立自身对象
    private static SingletonDemo01 instance = new SingletonDemo01();

    //全局公开的方法
    public static SingletonDemo01 getInstance() {
        return instance;
    }

}

class SingletonDemo01Test {
    public static void main(String[] args) {
        SingletonDemo01 instance = SingletonDemo01.getInstance();
        SingletonDemo01 instance2 = SingletonDemo01.getInstance();

        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        System.out.println(instance == instance2);
    }
}

三、懒汉式

目的:解决饿汉式可能存在的内存空间浪费的问题进行该进,不一上来就建立对象,而是在使用时再来建立对象。

3.一、懒汉式的代码以下:

public class SingletonDemo02 {

    //保证类只有一个实例,私有其构造器
    private SingletonDemo02() {

    }
    //建立自身对象,当时不用当即加载
    private static SingletonDemo02 instance;

    //全局公开的方法  synchronized做用:加锁  多线程进来时会不安全,效率较低
    public static synchronized SingletonDemo02 getInstance() {
        if(instance == null) {
            instance = new SingletonDemo02();
        }
        return instance;
    }

}

3.二、分析:代码中为什要使用synchronized关键字来进行上锁

考率一下多线程的状况下,若是没有上锁,两个线程A、B一前之后的很紧密的执行该方法,而此时A完成了初始化操做,可是尚未进行返回,B此时进入判断语句中,此时也为null,这样也会进行初始化操做,因而乎,就获得了两个对象了,违反了单例模式设计得原则。

3.三、弊端分析:

该方法使用了synchronized对一个返回得方法进行了上锁,该方法得执行效率会较慢。

四、DCL_懒汉式

目的:DCL_懒汉式又称为双重检测懒汉式,为了改进懒汉式效率不高的问题

4.一、该类的1版本的代码以下:

public class SingletonDemo03 {

    //保证类只有一个实例,私有其构造器
    private SingletonDemo03() {
    }
    //建立自身对象,当时不用当即加载 volatile做用:尽大可能的解决极端状况的问题
    private volatile static SingletonDemo03 instance;

    //全局公开的方法  synchronized做用:加锁  多线程进来时会不安全,效率较低
    public static SingletonDemo03 getInstance() {
        if(instance == null) {
            //定一次进来时加锁,后面进来时就不加锁了,提升了效率
            synchronized (SingletonDemo03.class) {
                if(instance == null) {
                    instance = new SingletonDemo03();
                }
            }
        }
        return instance;
    }

}

4.二、分析1版本代码:

一样考率多线程的状况下,A、B两线程相继的进入方法中,A率先得到初始化权力,进行上锁,进行对对象的建立,而且由于有volatile关键字,可以快速的将对象更新给B。若是B未进入判断语句中,则此时B中有该类对象了,直接返回了。若是B进入了判断语句中,可是A已经上锁了,也没法进入了,只有返回了。

4.三、1版本的弊端

一、再考虑多线程的极端状况,若是该类比较庞大,建立对象须要花费很长时间,B已经进入函数中了,而A建立对象的时间会比B走完该函数的时间长,则此时该函数将会返回B,而B=NULL。

二、该模式没法防止反射

4.四、版本2代码:

public class SingletonDemo03 {

    //破坏两次都用反射建立对象
    private static boolean flag = false;

    //保证类只有一个实例,私有其构造器
    private SingletonDemo03() {
        //防治被反射
        synchronized (SingletonDemo03.class) {
            if(flag == false) {
                flag = true;
            }else {
                throw new RuntimeException("不要试图用反射破坏单例");
            }
        }

    }
    //建立自身对象,当时不用当即加载 volatile做用:尽大可能的解决极端状况的问题
    private volatile static SingletonDemo03 instance;

    //全局公开的方法  synchronized做用:加锁  多线程进来时会不安全,效率较低
    public static SingletonDemo03 getInstance() {
        if(instance == null) {
            //定一次进来时加锁,后面进来时就不加锁了,提升了效率
            synchronized (SingletonDemo03.class) {
                if(instance == null) {
                    instance = new SingletonDemo03();
                }
            }
        }
        return instance;
    }

}

4.五、弊端分析

该版本一样未解决上面的问题,只是加大了反射获取对象的难度,反射破坏单例的代码以下:

class SingletonDemo03Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        /*
        SingletonDemo03 instance1 = SingletonDemo03.getInstance();
        SingletonDemo03 instance2 = SingletonDemo03.getInstance();

        System.out.println(instance1 == instance2);
*/
        Class<SingletonDemo03> clazz = SingletonDemo03.class;

        //反射破坏单例
        Constructor<SingletonDemo03> declaredConstructor = clazz.getDeclaredConstructor(null);

        declaredConstructor.setAccessible(true);

        SingletonDemo03 instance1 = declaredConstructor.newInstance();

        //破坏flag
        Field flag = clazz.getDeclaredField("flag");
        flag.setAccessible(true);
        flag.set(clazz,false);
        System.out.println(flag.get(clazz));


        SingletonDemo03 instance2 = declaredConstructor.newInstance();

        System.out.println(instance1 == instance2);

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

运行结果:设计模式

false
false
21685669
2133927002

五、静态内部类实现

该方式可以不适用synchronized提升效率,而且可以保证在多线程的状况下依旧是单例,代码以下:

public class SingletonDemo04 {
    private SingletonDemo04() {

        //防治被反射
        synchronized (SingletonDemo04.class) {
            if(InnerClass.instance != null) {
                throw new RuntimeException("不要试图用反射破坏单例");
            }
        }
    }

    private static class InnerClass {
        private static final SingletonDemo04 instance = new SingletonDemo04();
    }

    public static SingletonDemo04 getInstance() {
        return InnerClass.instance;
    }
}

六、利用枚举来实现

java中最为推荐的是使用枚举类来建立单例对象,由于枚举类有这纯自然的优点,没法被反射。点击进反射建立对象的newInstance()方法的源码中能够发现:

@CallerSensitive
public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

此外,枚举类也自己就是单例的,因此使用枚举类来建立单例对象最为适合,而现在大多数的框架的单例也都是经过这样的方法进行建立的。代码以下:

/**
 * 反射不能破坏枚举类型,枚举类纯自然的单例,最简单
 */
public enum SingletonDemo05 {
    INSTANCE;

    public SingletonDemo05 getInstance() {
        return INSTANCE;
    }

    public String hello() {
        return "Hello World!";
    }
}

class SingletonDemo05Test {
    public static void main(String[] args) {
        SingletonDemo05 instance1 = SingletonDemo05.INSTANCE;
        SingletonDemo05 instance2 = SingletonDemo05.INSTANCE.getInstance();

        System.out.println(instance1 == instance2);

        String hello = SingletonDemo05.INSTANCE.hello();
        System.out.println(hello);
    }
}
相关文章
相关标签/搜索