【设计模式】对于单例模式的理解

目录

单例模式的定义

线程唯一

进程唯一

 懒汉式(线程不安全)

饿汉式

懒汉式(线程安全) 

 内部静态类

双检锁

枚举单例

 集群唯一


单例模式的定义

单例的定义:“一个类只允许创建唯一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。”

百度百科:

线程唯一

如何实现一个线程唯一的单例模式呢?

首先线程唯一,那就首先想到了此时线程要作为一个映射的key,然后取这个值。

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static final ConcurrentHashMap<Long, IdGenerator> instances = new ConcurrentHashMap<>();

    private IdGenerator() {
    }

    public static IdGenerator getInstance() {
        Long currentThreadId = Thread.currentThread().getId();
        instances.putIfAbsent(currentThreadId, new IdGenerator());
        return instances.get(currentThreadId);
    }

    public long getId() {
        return id.incrementAndGet();
    }
}

 还有一种方式就是用ThreadLocal了,原理其实差不多,只不过这个映射关系直接被放在Thread类里面了。

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    
    private static final ThreadLocal<IdGenerator> instances = new ThreadLocal<>();

    private IdGenerator() {

    }

    public static IdGenerator getInstance() {
      final IdGenerator idGenerator = instances.get();
      if(idGenerator == null){
        final IdGenerator value = new IdGenerator();
        instances.set(value);
        return value;
      }
      return idGenerator;
    }

    public long getId() {
        return id.incrementAndGet();
    }
}

进程唯一

 懒汉式(线程不安全)

这也是最经典的一种实现。是有线程安全问题的。

public class LazySingleton {

    private static LazySingleton instance;

    public LazySingleton() {

    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

饿汉式

用空间换时间,在线程还没出现之前就已经实例化了,是线程安全的。

public class EagerSingleton {

    private static final EagerSingleton instance = new EagerSingleton();

    //防止外部实例化
    private EagerSingleton(){

    }

    public static EagerSingleton getInstance(){
        return instance;
    }

    public static void main(String[] args) {
        EagerSingleton eagerSingleton = EagerSingleton.getInstance();
    }
}

懒汉式(线程安全) 

public class LazySingleton {

    private static LazySingleton instance;

    public LazySingleton() {

    }

    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

 内部静态类

使用类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不会因为加锁的方式耗费性能。

这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确的加载。

public class InnerSingleton {

    private static class SingletonHolder {
        private static InnerSingleton instance = new InnerSingleton();
    }

    private InnerSingleton() {
    }

    public static InnerSingleton getInstance() {
        return SingletonHolder.instance;
    }

}

双检锁

本质上还是懒汉式,是线程安全的,instance需要被volatile修饰否则会有线程安全问题。


这个问题在于 EagerSingleton instance=new EagerSingleton() 这行代码并不是一个原子指令。使用 javap -c指令,可以快速查看字节码。

	// 创建 EagerSingleton 对象实例,分配内存
       0: new           #5                  // class com/cx/EagerSingleton
       // 复制栈顶地址,并再将其压入栈顶
       3: dup
	// 调用构造器方法,初始化 Cache 对象
       4: invokespecial #6                  // Method "<init>":()V
	// 存入局部方法变量表
       7: astore_1

从字节码可以看到创建一个对象实例,可以分为三步:

  1. 分配对象内存
  2. 调用构造器方法,执行初始化
  3. 将对象引用赋值给变量。

虚拟机实际运行时,以上指令可能发生重排序。以上代码 2,3 可能发生重排序,但是并不会重排序 1 的顺序。也就是说 1 这个指令都需要先执行,因为 2,3 指令需要依托 1 指令执行结果。

Java 语言规规定了线程执行程序时需要遵守 intra-thread semantics。**intra-thread semantics ** 保证重排序不会改变单线程内的程序执行结果。这个重排序在没有改变单线程程序的执行结果的前提下,可以提高程序的执行性能。

虽然重排序并不影响单线程内的执行结果,但是在多线程的环境就带来一些问题。


 volatile的底层使用CPU指令禁止重排序,保证了多线程的安全性。

public class LazySingleton {

    private static volatile LazySingleton instance;

    public LazySingleton() {

    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }

}

枚举单例

public enum EnumSingleton {
    INSTANCE;

    // 添加其他成员变量和方法

    public void doSomething() {
        // 单例实例的操作
    }
}

 集群唯一

  • 我们需要把这个单路模式序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例模式的时候,需要先从外部共享存储器中并将它读取到内存,并反序列化成对象,然后再使用,使用完成后还需要再存储回外部存储区。
  • 为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,还需要显式地将对象从内存中删除,并且释放对对象的加锁。
public class IdGenerator {
	private AtomicLong id = new AtomicLong(0);
	private static IdGenerator instance;
	private static SharedObjectStorage storage = FileSharedObjectStorage(/*入参省略*/);
	private static DistributedLock lock = new DistributedLock();
	
	private IdGenerator() {}
	
	public synchronized static IdGenerator getInstance()
		if (instance == null) {
			lock.lock();
			instance = storage.load(IdGenerator.class);
		}
		return instance;
	}
	
	public synchroinzed void freeInstance() {
		storage.save(this, IdGeneator.class);
		instance = null; //释放对象
		lock.unlock();
	}
	
	public long getId() {
		return id.incrementAndGet();
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值