zoukankan      html  css  js  c++  java
  • 抽丝剥茧设计模式-你真的懂单例模式吗?

    一、概述 

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

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

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

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

    二、单例模式的9种写法

    1.饿汉式

    /*
     * 饿汉式,类加载到内存后,就实例化一个单例,JVM保证线程安全。
     * 优点:简单实用,推荐使用。
     * 缺点:不管用到与否,类装载时就完成实例化,Class.forName("")。
     */
    public class HungrySingleton {
        private static final HungrySingleton INSTANCE = new HungrySingleton();
        private HungrySingleton() {
        }
        public static HungrySingleton getInstance() {
            return INSTANCE;
        }
    }
    

    2.静态代码块饿汉式

    /**
     * 饿汉式变种
     */
    public class StaticHungrySingleton {
        private static final StaticHungrySingleton INSTANCE;
        static {
            INSTANCE = new StaticHungrySingleton();
        }
        private StaticHungrySingleton() {
        }
        public static StaticHungrySingleton getInstance() {
            return INSTANCE;
        }
    }
    

      

    3.普通懒汉式

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

    4.懒汉式升级

    //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.静态内部类式

    /*
    静态内部类方式,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.枚举式

    /**
     * 解决懒加载、线程同步,还可以防止反射、反序列化。
     * 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方式

    /**
     * 通过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方式

    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

    /*
    cas是一项乐观锁技术,当多个线程尝试使用cas同时更新一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知竞争失败,并可以再次尝试。
    优点:本质是基于忙等待算法,依赖底层硬件的实现。没有线程切换和阻塞的额外消耗。
    缺点:一直执行不成功,对cpu造成较大的开销。
    */

    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语言的反射机制。摘自: 百度百科

    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.反序列化可以破坏单例模式

     

    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();
            }
        }
    }

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

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

     

  • 相关阅读:
    [erlang] Erlang继承(inheritance)
    [python]python 动态调用模块&类&方法
    [mysql]将mysql输入内容保存文件
    [erlang] Erlang TCP(gen_tcp)
    hdu 3350 #define is unsafe && hdu3328 Flipper
    hdu 1690 Bus System
    hdu 1401 Solitaire (双向广搜)
    hdu3172 Virtual Friends (并查集+字典树)
    hdu1426 Sudoku Killer
    hdu3111 Sudoku (精确覆盖解数独 DLX)
  • 原文地址:https://www.cnblogs.com/wscl/p/15156051.html
Copyright © 2011-2022 走看看