单例模式
概述
在程序运行的时候,通常会产生很多实例,例如String类和字符串是一对一的关系,当创建了1000个字符串时,就会有1000个实例生成。但是在实际开发过程中,我们需要在程序中,某个东西只会存在一个,就会有只能创建一个实例的需求。例如在程序中开辟缓存时,缓存对象我们只希望他创建一次。
原则
- 私有构造(组织类被通过常规方法实例化)
- 以静态方法或枚举返回实例(保证实例的唯一性)
- 确保实例只有一个,尤其是多线程环境(保证实例在创建时的线程安全)
- 确保反序列化时不会重新构建对象
静态类使用
这种方法在程序内部做缓存时非常的常见,ConcurrentHashMap 的优势在于兼顾性能和线程安全,一个线程在进行写操作时,它会锁住一小部分,其他部分的读写不受影响,其他线程访问没上锁的地方不会被阻塞
public class Singleton00 {
private static Map<String,Object> cache = new ConcurrentHashMap<>();
}
饿汉模式
饿汉模式指的是在程序启动时直接创建对象,外部调用时只能通过getInstance方法来获取实例,这种情况不太适用于初始化对象很复杂的情况,会导致程序启动速度下降。构造方法使用private为了强制要求使用getInstance方法获取实例
public class Singleton01 {
private static Singleton01 singleton01 = new Singleton01();
private Singleton01() {
}
public static Singleton01 getInstance() {
return singleton01;
}
}
//测试
@SpringBootTest
class Practice800ApplicationTests {
@Test
void contextLoads() {
Singleton01 instance1 = Singleton01.getInstance();
Singleton01 instance2 = Singleton01.getInstance();
if (instance1 == instance2){
System.out.println("两者相同");
}
}
}
懒汉模式(线程不安全)
懒汉模式指的是在对象在第一次使用时才被创建,但这种创建模式是线程不安全的,如果有多个线程一起访问getInstance方法,可能会创建多个Singleton02对象
public class Singleton02 {
private static Singleton02 singleton02;
private Singleton02() {
}
public static Singleton02 getInstance() {
if (singleton02 != null){
return singleton02;
}
singleton02 = new Singleton02();
return singleton02;
}
}
懒汉模式(线程安全)
使用synchronized修饰getInstance方法,可以保证该实例的创建是线程安全的
public class Singleton03 {
private static Singleton03 singleton03;
private Singleton03() {
}
public static synchronized Singleton03 getInstance() {
if (singleton03 != null){
return singleton03;
}
singleton03 = new Singleton03();
return singleton03;
}
}
但随之而来的问题是,这样对于创建完对象后,调用getInstance方法也会需要排队,效率会随之下降,我们可以使用Volatile关键字+synchronized代码块来解决
被Volatile关键字修饰的对象,具有可见性,也就是说当一个线程修改该对象,另外的线程可以感知到对象的变化
public class Singleton04 {
private static volatile Singleton04 singleton04;
private Singleton04() {
}
public static Singleton04 getInstance(){
if (singleton04 != null){
return singleton04;
}
synchronized (Singleton04.class){
if (null == singleton04){
singleton04 = new Singleton04();
}
}
return singleton04;
}
}
使用类的内部类
使用类的静态内部类实现的单例模式,既保证了线程安全又保证了懒加载,同时不会因为加锁的方式耗费性能。
public class Singleton05 {
private static class SingletonHolder{
private static Singleton05 singleton05 = new Singleton05();
}
private Singleton05() {
}
public static Singleton05 getInstance(){
return SingletonHolder.singleton05;
}
}
CAS「AtomicReference」(线程安全)
使用CAS的好处就是不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性。
public class Singleton06 {
private static final AtomicReference<Singleton06> INSTANCE = new AtomicReference<>();
private Singleton06() {
}
public static Singleton06 getInstance(){
for ( ; ; ){
//get() 获取AtomicReference的当前对象引用值。
Singleton06 singleton06 = INSTANCE.get();
if (null != singleton06) {
return singleton06;
}
//compareAndSet(expect,update),更新引用值
//expect指的是当前的对象的值,update则是需要设置的新引用值。
INSTANCE.compareAndSet(null,new Singleton06());
return INSTANCE.get();
}
}
}