JUC - 多线程之 单例模式(八)

单例模式(Singleton Pattern)是一种非常简单的设计模式之一,当我们使用的对象要在全局唯一时就需要用到该模式,以保证对象的唯一性。除此之外,还能避免反复的实例化对象,减少内存开销

单例类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

单例主要有如下创建方式

一、饿汉式--静态变量(线程安全

/**
 * 1、饿汉式-静态变量(线程安全)
 */
public class Hungry1 {
    // 可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];

    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Hungry1(){
    }

    // 2、内部创建对象
    private static Hungry1 singleton = new Hungry1();

    // 3、对外获取实例的方法
    public static Hungry1 getInstance(){
        return singleton;
    }
}

优点:写法简单;避免了线程同步问题

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费 

二、饿汉式--静态代码块(线程安全

/**
 * 2、饿汉式-静态代码块(线程安全)
 */
public class Hungry2 {
    // 可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];

    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Hungry2(){
    }

    // 2、内部静态代码块中创建对象
    private static Hungry2 singleton;
    static{
        singleton = new Hungry2();
    }

    // 3、对外获取实例的方法
    public static Hungry2 getInstance(){
        return singleton;
    }
}

优缺点同上 

三、懒汉式--简单判断非空(多线程并发不安全,单线程无影响)

/**
 * 3、懒汉式-简单判断非空(多线程并发不安全,单线程无影响)
 */
public class Lazy1 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy1() {}

    // 2、声明类成员变量singleton
    private static Lazy1 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy1 getInstance(){
        if (singleton == null){
            singleton = new Lazy1();
        }
        return singleton;
    }
}

优点:在使用时才会生成对象,能够减少内存开销

缺点:线程不安全,只适用单线程,当有多个线程访问时,能够产生多个对象,不满足单例模式的要求

在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例

四、懒汉式--synchronized锁方法(线程安全,效率低)

/**
 * 4、懒汉式-synchronized锁方法(线程安全,效率低)
 */
public class Lazy2 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy2() {}

    // 2、声明类成员变量singleton
    private static Lazy2 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static synchronized Lazy2 getInstance(){
        if (singleton == null){
            singleton = new Lazy2();
        }
        return singleton;
    }
}

优点:线程安全

缺点:效率太低synchronized锁了整个方法,下一个线程必须等上一个线程释放锁,效率很低,不建议使用

五、懒汉式--synchronized同步代码块(多线程并发不安全)

/**
 * 5、懒汉式-synchronized同步代码块(多线程并发不安全)
 */
public class Lazy3 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy3() {}

    // 2、声明类成员变量singleton
    private static Lazy3 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static synchronized Lazy3 getInstance(){
        if (singleton == null){
            synchronized (Lazy3.class){
                singleton = new Lazy3();
            }
        }
        return singleton;
    }
}

优点:在使用时才会生成对象,能够减少内存开销

缺点:线程不安全

假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例

六、懒汉式--双重检查DCL(线程安全;常用)

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 */
public class Lazy4 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4() {}

    // 2、声明类成员变量singleton
    private volatile static Lazy4 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4 getInstance(){
        if (singleton == null){
            synchronized (Lazy4.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常,最好加上volatile修饰
                     */
                    singleton = new Lazy4();
                }
            }
        }
        return singleton;
    }
}

Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象 

优点:效率高,线程安全

缺点:可通过反射破坏单例模式 

反射破坏双重懒汉式单例

1、反射破坏双重懒汉式单例--破坏私有无参构造器

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 *
 * 1、反射破坏双重懒汉式单例--破坏私有无参构造器
 */
public class Lazy4Reflect {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4Reflect() {}

    // 2、声明类成员变量singleton
    private static Lazy4Reflect singleton;
    //private static boolean isReflect = false;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4Reflect getInstance(){
        if (singleton == null){
            synchronized (Lazy4Reflect.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常
                     */
                    singleton = new Lazy4Reflect();
                }
            }
        }
        return singleton;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        Lazy4Reflect instance = Lazy4Reflect.getInstance();
        Constructor<Lazy4Reflect> constructor = Lazy4Reflect.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        Lazy4Reflect instance1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

 输出如下,两个 instance内存地址明显不同

2、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁也破坏

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 *
 * 2、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁也破坏
 */
public class Lazy4Reflect1 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4Reflect1() {
        synchronized (Lazy4Reflect1.class){
            if (singleton != null){
                throw new RuntimeException("不要使用反射破坏异常");
            }
        }
    }

    // 2、声明类成员变量singleton
    private static Lazy4Reflect1 singleton;
    //private static boolean isReflect = false;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4Reflect1 getInstance(){
        if (singleton == null){
            synchronized (Lazy4Reflect1.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常
                     */
                    singleton = new Lazy4Reflect1();
                }
            }
        }
        return singleton;
    }
}

class Test1 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        //Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();
        Constructor<Lazy4Reflect1> constructor = Lazy4Reflect1.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        // 两个对象都不经过 getInstance()创建,反射拿到
        Lazy4Reflect1 instance = constructor.newInstance();
        Lazy4Reflect1 instance1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

3、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁,加标志位也破坏

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 *
 * 3、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁,加标志位也破坏
 */
public class Lazy4Reflect2 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4Reflect2() {
        synchronized (Lazy4Reflect2.class){
            if (isReflect = false){
                isReflect = true;
            }else {
                throw new RuntimeException("不要使用反射破坏异常");
            }
        }
    }

    // 2、声明类成员变量singleton
    private static Lazy4Reflect2 singleton;
    private static boolean isReflect = false;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4Reflect2 getInstance(){
        if (singleton == null){
            synchronized (Lazy4Reflect2.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常
                     */
                    singleton = new Lazy4Reflect2();
                }
            }
        }
        return singleton;
    }
}

class Test2 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        // 反射破坏单例
        Field isReflectField = Lazy4Reflect2.class.getDeclaredField("isReflect");
        isReflectField.setAccessible(true);

        //Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();
        Constructor<Lazy4Reflect2> constructor = Lazy4Reflect2.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        // 两个对象都不经过 getInstance()创建,反射拿到
        Lazy4Reflect2 instance = constructor.newInstance();
        isReflectField.set(instance,false);
        Lazy4Reflect2 instance1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

七、静态内部类(线程安全;常用)

/**
 * 7、静态内部类(线程安全;常用)
 */
public class StaticInnerClass {
    // 1、将构造方法私有化,外部无法使用new
    private StaticInnerClass(){

    }

    // 2、一个静态内部类 创建实例
    private static class StaticInstance{
        private static final StaticInnerClass INSTENCE  = new StaticInnerClass();
    }

    // 3、直接调用静态内部类,返回instance
    public static StaticInnerClass getInstance(){
        return StaticInstance.INSTENCE;
    }
}

这种方式跟饿汉式方式采用的机制类似,但又有不同。

两者都是采用了类装载的机制来保证初始化实例时只有一个线程。

不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的 

优点:线程安全,延迟加载,效率高

缺点: 可通过反射破坏单例模式 

反射破坏

/**
 * 7、静态内部类(线程安全;常用)
 * 
 * 可以通过反射破坏
 */
public class StaticInnerClass {
    // 1、将构造方法私有化,外部无法使用new
    private StaticInnerClass(){

    }

    // 2、一个静态内部类 创建实例
    private static class StaticInstance{
        private static final StaticInnerClass INSTENCE  = new StaticInnerClass();
    }

    // 3、直接调用静态内部类,返回instance
    public static StaticInnerClass getInstance(){
        return StaticInstance.INSTENCE;
    }
}


class TestStaticInner{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        StaticInnerClass instance = StaticInnerClass.getInstance();
        Constructor<StaticInnerClass> constructor = StaticInnerClass.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        StaticInnerClass instance1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

八、枚举(线程安全;可防止反序列化;强烈推荐!!!)

/**
 * 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)
 */
public enum EnumSingleton {
    INSTANCE;

    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

优点:线程安全,不用担心反射破坏单例模式

缺点:枚举类占用内存多

构造器源码

public final class Constructor<T> extends Executable {
    @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;
    }
}

尝试使用反射破坏

/**
 * 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)
 */
public enum EnumSingleton {
    INSTANCE;

    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

class TestEnumSingleton{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        EnumSingleton instance = EnumSingleton.INSTANCE;

        // java.lang.NoSuchMethodException
        //Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(null);

        // 注:通过 jad 反编译得到 有参构造
        Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);

        // 忽略私有构造器
        constructor.setAccessible(true);

        // java.lang.NoSuchMethodException
        EnumSingleton instance1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

通过 jad 反编译得到 有参构造 

  

 直接报错

单例模式分为懒汉式、饿汉式、饿汉式同步锁、双重校验锁、静态内部类、枚举类,每一种都有自己的优缺点,可以根据自己的项目实际需要选择适合的单例模式

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值