一、概述
单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。防止一个全局使用的类频繁地创建与销毁。
应用场景: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(); } } }
感谢阅读到现在,请在留言区提出宝贵的意见!