一、静态类使用 import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 静态类使用 */ public class Singleton_00 { public static Map<String, String> cache = new ConcurrentHashMap<>(); /** * 以上这种方式在我们平常的业务开发中非常常见,这样静态类的方式可以在第一次运行行的时候直 * 接初始化Map类,同时这里我们也不需要到延迟加载在使用。 * 在不需要维持任何状态下,仅仅用于全局访问,这个使用静态类的方式更加方便。 * 但如果需要被继承以及需要维持⼀些特定状态的情况下,就适合使用单例模式。 */ }
二、汉模式(线程不不安全) /** * 懒汉模式(线程不不安全) */ public class Singleton_01 { private static Singleton_01 singleton01; public static Singleton_01 getInstance(){ if ( null != singleton01 ) { return singleton01; } singleton01 = new Singleton_01(); return singleton01; } /** * 单例例模式有一个特点就是不允许外部直接创建,也就是new Singleton_01() ,因此这里在默认的构造函数上添加了私有属性 private 。 * 目前此种方式的单例例确实满足了懒加载,但是如果有多个访问者同时去获取对象实例,你可以想象成⼀堆人在抢厕所 ,就会造成多个同样的实例例并存,从而没有达到单例例的要求。 */ }
三、懒汉式(线程安全) /** * 懒汉式(线程安全) */ public class Singleton_02 { private static Singleton_02 instance; public static synchronized Singleton_02 getInstance(){ if (null != instance){ return instance; } instance = new Singleton_02(); return instance; } /** * 此种模式虽然是安全的,但由于把锁加到方法上后,所有的访问都因需要锁占⽤用导致资源的浪费。 * 如果不是特殊情况下,不建议此种⽅式实现单例模式 */ }
四、饿汉模式(线程安全)
/** * 饿汉模式(线程安全) */ public class Singleton_03 { private static Singleton_03 instance = new Singleton_03(); public static Singleton_03 getInstance(){ return instance; } /** * 此种方式与我们开头的第一个实例化Map基本一致,在程序启动的时候直接运行加载,后续有外 * 部需要使用的时候获取即可。 * 但此种方式并不是懒加载,也就是说无论你程序中是否用到这样的类都会在程序启动之初进行创 * 建。 * 那么这种方式导致的问题就像你下载个游戏软件,可能你游戏地图还没有打开呢,但是程序已经将 * 这些地图全部实例化。到你手机上最明显体验就一开游戏内存满了,⼿机卡了,需要换了 */ }
五、使用类的内部类(线程安全)
/**
* 使用类的内部类(线程安全)
*/
public class Singleton_04 {
private static class SingletonHolder{
private static Singleton_04 instance = new Singleton_04();
}
public static Singleton_04 getInstance(){
return SingletonHolder.instance;
}
/** 使用类的静态内部类实现的单例模式,既保证了线程安全又保证了懒加载,同时不会因为加锁的方式耗费性能。
* 这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环
* 境下可以被正确的加载。
* 此种方式也是非常推荐使用的一种单例例模式
*/
}
六、双重锁校验(线程安全) /** * 双重锁校验(线程安全) */ public class Singleton_05 { private static Singleton_05 instance; public static Singleton_05 getInstance(){ if (null != instance){ return instance; } synchronized (Singleton_05.class){ if (null == instance){ instance = new Singleton_05(); } } return instance; } /** 双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时。 * 同时这种方式也满足了懒加载。 */ } 七、AS[AtomicReference](线程安全)
import java.util.concurrent.atomic.AtomicReference; /** * CAS[AtomicReference](线程安全) */ public class Singleton_06 { private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<>(); private static Singleton_06 instance; public static final Singleton_06 getInstance() { for (; ; ) { instance = INSTANCE.get(); if (null != instance) { return instance; } INSTANCE.compareAndSet(null, new Singleton_06()); return INSTANCE.get(); } } public static void main(String[] args) { System.out.println(Singleton_06.getInstance());//com.lrkj.singleton.Singleton_06@5acf9800 System.out.println(Singleton_06.getInstance());//com.lrkj.singleton.Singleton_06@5acf9800 } /** ava并发库提供了很多原子类来支持并发访问的数据安全 * 性; AtomicInteger 、 AtomicBoolean 、 AtomicLong 、 AtomicReference 。 * AtomicReference 可以封装引用一个V实例,⽀持并发访问如上的单例⽅式就是使用了这样的一个特点。 * 使⽤用CAS的好处就是不需要使用传统的加锁方式保证线程安全,⽽而是依赖于CAS的忙等算法,依赖 * 于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外 * 的开销,并且可以支持较大的并发性。 * 当然CAS也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。 */ } 八、枚举单例(线程安全)
/** * 枚举单例(线程安全) */ public enum Singleton_07 { INSTANCE; public void test(){ System.out.println("hi"); } /** Effective Java 作者推荐使用枚举的方式解决单例模式,此种方式可能是平时最少用到的。 * 这种方式解决了最主要的;线程安全、⾃由串行化、单一实例。 * 这种写法在功能上与共有域方法相近,但是它更简洁,⽆偿地提供了串行化机制,绝对防止对此实例例化,即使是在⾯面对复杂的串行化或者反射攻击的时候。虽然这中⽅法还没有⼴泛采用,但是单元素的枚举类型已经成为实现 Singleton 的最佳方法。 * 但也要知道此种⽅式在存在继承场景下是不可用的。 */ }
枚举单例调用方式 package com.lrkj.singleton.test; import com.lrkj.singleton.Singleton_07; import org.junit.Test; /** * 枚举单例调用方式 */ public class Singleton07Test { @Test public void test(){ Singleton_07.INSTANCE.test(); } }