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

一、概述 

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

  应用场景: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();
        }
    }
}

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

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

 

原文地址:https://www.cnblogs.com/wscl/p/15156051.html