抽丝剥茧设计模式-你真的懂单例模式吗?

2021/8/18 23:11:44

本文主要是介绍抽丝剥茧设计模式-你真的懂单例模式吗?,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、概述 

  单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。防止一个全局使用的类频繁地创建与销毁。

  应用场景:Spring中的bean、计数器等。

  关键代码:构造函数是私有的。

  接下来介绍10种单例模式写法,有点像孔乙己里面茴字有多种写法一样,其实只要会用一种即可。搞这么多还不是为了装x。

二、单例模式的9种写法

1.饿汉式

1

2

3

4

5

6

7

8

9

10

11

12

13

/*

 * 饿汉式,类加载到内存后,就实例化一个单例,JVM保证线程安全。

 * 优点:简单实用,推荐使用。

 * 缺点:不管用到与否,类装载时就完成实例化,Class.forName("")。

 */

public class HungrySingleton {

    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton() {

    }

    public static HungrySingleton getInstance() {

        return INSTANCE;

    }

}

2.静态代码块饿汉式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

/**

 * 饿汉式变种

 */

public class StaticHungrySingleton {

    private static final StaticHungrySingleton INSTANCE;

    static {

        INSTANCE = new StaticHungrySingleton();

    }

    private StaticHungrySingleton() {

    }

    public static StaticHungrySingleton getInstance() {

        return INSTANCE;

    }

}

  

3.普通懒汉式

  不举例子了,普通写法多线程下有问题。

4.懒汉式升级

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

//DoubleCheckLock 双重检查锁 懒汉式

public class DclSingleton {

    //加上volatile关键字,禁止指令重拍,防止多线程的情况下,返回为初始化完成的对象。

    private static volatile DclSingleton INSTANCE;

    private DclSingleton() {

    }

    public static DclSingleton getInstance() {

        if (INSTANCE == null) {

            synchronized (DclSingleton.class) {

                if (INSTANCE == null) {

                    INSTANCE = new DclSingleton();

                }

            }

        }

        return INSTANCE;

    }

}

  

5.静态内部类式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

/*

静态内部类方式,JVM保证单例,加载外部类时不会加载内部类,可以实现懒加载。

 */

public class StaticInnerClassSingleton {

    private StaticInnerClassSingleton() {

    }

    private static class StaticInnerClassSingletonHolder {

        private final static StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();

    }

    public static StaticInnerClassSingleton getInstance() {

        return StaticInnerClassSingletonHolder.INSTANCE;

    }

}

  

6.枚举式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

/**

 * 解决懒加载、线程同步,还可以防止反射、反序列化。

 * Effective Java 作者 Josh Bloch推荐的写法。

 */

public enum EnumSingleton {

    INSTANCE;

    public void method() {

        System.out.println("I am a function");

    }

    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {

            new Thread(() -> {

                System.out.println(EnumSingleton.INSTANCE.hashCode());

                EnumSingleton.INSTANCE.method();

            }).start();

        }

    }

}

7.ThreadLocal方式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

/**

 * 通过thread local

 */

public class ThreadLocalSingleton {

    private static final ThreadLocal<ThreadLocalSingleton> tlSingleton = new ThreadLocal<ThreadLocalSingleton>() {

        @Override

        protected ThreadLocalSingleton initialValue() {

            return new ThreadLocalSingleton();

        }

    };

    private ThreadLocalSingleton() {

    }

    public static ThreadLocalSingleton getInstance() {

        return tlSingleton.get();

    }

}

 

8.Lock方式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public class LockSingleton {

    private static LockSingleton instance = null;

    private static Lock lock = new ReentrantLock();

    private LockSingleton() {

    }

    public static LockSingleton getInstance() {

        if (null == instance) {

            lock.lock();//显示调用,手动加锁

            if (instance == null) {

                instance = new LockSingleton();

            }

            lock.unlock();//显示调用,手动加锁

        }

        return instance;

    }

}

  

上面的几种实现方式原理都是借助类类加载的时候初始化单例,即ClassLoader的线程安全机制。就是ClassLoader的loadClass方法在加载类的时候,使用了synchronized关键字。其实底层还是使用了synchronized关键字。
 

9.CAS

1

2

3

4

/*

cas是一项乐观锁技术,当多个线程尝试使用cas同时更新一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知竞争失败,并可以再次尝试。

优点:本质是基于忙等待算法,依赖底层硬件的实现。没有线程切换和阻塞的额外消耗。

缺点:一直执行不成功,对cpu造成较大的开销。

1

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<br>

public class CasSingleton {

    private static final AtomicReference<CasSingleton> INSTANCE = new AtomicReference<CasSingleton>();

    private CasSingleton() {

    }

    public static CasSingleton getInstance() {

        for (; ; ) {

            CasSingleton casSingleton = INSTANCE.get();

            if (null != casSingleton) {

                return casSingleton;

            }

            casSingleton = new CasSingleton();

            if (INSTANCE.compareAndSet(null, casSingleton)) {

                return casSingleton;

            }

        }

    }

}

  

三.扩展知识

1.一道面试题

题面:

  不使用synchronized和lock实现一个单例模式?——商汤

答:

  饿汉式、静态内部类、枚举、cas

2.Java反射可以破坏单例模式

   JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。摘自: 百度百科

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

import java.lang.reflect.Constructor;

import java.lang.reflect.InvocationTargetException;

public class ReflectDestroySingleton {

    public static void main(String[] args) {

        try {

            Class clazz = DclSingleton.class;

            Constructor constructor = clazz.getDeclaredConstructor(null);

            constructor.setAccessible(true);

            Object obj1 = constructor.newInstance();

            Object obj2 = constructor.newInstance();

            System.out.println(obj1 == obj2);

        catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {

            e.printStackTrace();

        }

    }

}

3.反序列化可以破坏单例模式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

import java.io.*;

public class SerializationDestroySingleton {

    public static void main(String[] args) {

        HungrySingleton hungrySingleton = HungrySingleton.getInstance();

        System.out.println(hungrySingleton);

        try {

            //实例序列化到磁盘

            FileOutputStream fileOutputStream = new FileOutputStream("hungrySingleton");

            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

            objectOutputStream.writeObject(hungrySingleton);

            objectOutputStream.flush();

            objectOutputStream.close();

            //从磁盘反序列化

            FileInputStream fileInputStream = new FileInputStream("hungrySingleton");

            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

            HungrySingleton object = (HungrySingleton) objectInputStream.readObject();

            System.out.println(object);

            System.out.println(object == hungrySingleton);

        catch (

                Exception e) {

            e.printStackTrace();

        }

    }

}

感谢阅读到现在,请在留言区提出宝贵的意见!

更多精彩内容,关注微信公众号:技术严选

 



这篇关于抽丝剥茧设计模式-你真的懂单例模式吗?的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程