原文链接:www.upheart.top/2020/03/23/…
保证一个类只有一个实例,并且提供一个全局访问点
public class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return INSTANCE; } } 复制代码
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
public class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } } 复制代码
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
public class Singleton { private static Singleton singleton; private Singleton() {} public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } } 复制代码
解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。 缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
public class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } } 复制代码
这种同步并不能起到线程同步的作用。假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例,线程1获得锁实例化,释放锁,线程2获得锁继续实例化就会有问题
public class Singleton { private static volatile Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 复制代码
但是还是有个小问题的,问题就在singleton= new Singleton();语句上。 这语句在这里看起来是一句代码啊,但实际上它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致做了3件事情:
编译器(JIT),CPU 有可能对指令进行重排序,上面的第二和第三的顺序是无法保证的。也就是说,执行顺序可能是1-2-3也可能是1-3-2.如果是后者,并且在3执行完毕、2未执行之前,被切换到线程B上,这时候singleton因为已经在线程A内执行3了,instance已经是非null,所有线程B直接取走singleton,再使用时就会出错,这就是DCL失效问题,而且这种难以跟踪难以重现的问题很可能会隐藏很久。
public class Singleton { private static volatile Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } 复制代码
public class Singleton { private Singleton() {} private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.INSTANCE; } } 复制代码
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM的类加载器帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
Constructor<InnerClassSingleton> declaredConstructor=InnerClassSingleton.class.getDeclaredConstructor(); declaredConstructor.setAccessible( true ); InnerClassSingleton innerClassSingleton=declaredConstructor.newInstance(); InnerClassSingleton instance=InnerClassSingleton.getInstance(); System.out.println(innerClassSingleton==instance); //静态内部类防止反射破坏: private InnerClassSingleton(){ if (InnerClassHolder.instance!=null){ throw new RuntimeException( " 单例不允许多个实例 " ); } } 复制代码
public enum Singleton { INSTANCE } 复制代码
构造方法是私有的也只能私有,不允许被外部实例化,符合单例 天然不支持反射创建对应的实例
没有延时初始化,随着类的初始化就初始化了
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
可以利用 指定方法来替换从反序列化流中的数据 如下
Object readResolve() throws ObjectStreamException; class InnerClassSingleton implements Serializable{ static final long serialVersionUID = 42L; private static class InnerClassHolder{ private static InnerClassSingleton instance=new InnerClassSingleton(); } private InnerClassSingleton(){ if (InnerClassHolder.instance!=null){ throw new RuntimeException( " 单例不允许多个实例 " ); } } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } Object readResolve() throws ObjectStreamException{ return InnerClassHolder.instance;//保证了序列化和非序列化的实例对象一样 } } 复制代码
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。实现单例的方式如下:
public class Singleton { private static final AtomicReference INSTANCE = new AtomicReference(); private Singleton() {} public static Singleton getInstance() { for (;;) { Singleton singleton = INSTANCE.get(); if (null != singleton) { return singleton; } singleton = new Singleton(); if (INSTANCE.compareAndSet(null, singleton)) { return singleton; } } } } 复制代码
用CAS的好处在于不需要使用传统的锁机制来保证线程安全,CAS是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。 CAS的一个重要缺点在于如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。
另外,如果N个线程同时执行到singleton = new Singleton();的时候,会有大量对象创建,很可能导致内存溢出。
// Spring & JDK java.lang.Runtime org.springframework.aop.framework.ProxyFactoryBean org.springframework.beans.factory.support.DefaultSingletonBeanRegistry org.springframework.core.ReactiveAdapterRegistry // Tomcat org.apache.catalina.webresources.TomcatURLStreamHandlerFactory // 反序列化指定数据源 java.util.Currency 复制代码