文章目录
推荐:
- 枚举
- 饿汉静态
- 懒汉 DCL
初始化对象的单个步骤
- 分配内存空间
- 初始化
- 指向分配的内存地址
1. 懒汉
/**
* @Date: 2022-03-08 11:34
* @Author: liushengxi
* @Description: 懒加载
*/
public class LazyInstance {
/**
* 单例模式,懒汉式,线程不安全
*/
public static class Singleton2 {
private static Singleton2 instance = null;
private Singleton2() {
}
public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
/**
* 单例模式,懒汉式,线程安全,多线程环境下效率不高
*/
public static class Singleton3 {
private static Singleton3 instance = null;
private Singleton3() {
}
// 这里直接串行化了
public static synchronized Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
}
2. 懒汉式之-----双重检测(线程安全【推荐】
)
为什么需要两次检测呐?
- 第一次加锁是为了减少 synchronized 的开销,如果第一次检测的singleton 不为 null ,则不需要进行加锁初始化操作,因此可以大幅度降低 synchronized 带来的性能开销。
- 去掉第二个判断为空:即懒汉式(线程不安全),这会出现 线程A先执行了getInstance()方法,同时线程B因为同步锁而在外面等待,等到A线程已经创建出来一个实例出来并且执行完同步处理后,B线程将获得锁并进入同步代码,如果这时B线程不去判断是否已经有一个实例了,然后直接再new一个。这时就会有两个实例对象,即破坏了设计的初衷。(即线程不安全,效率高)
为什么要volatile呢?
- synchronized虽然保证了原子性,但是却没有保证重排序的正确性,会出现 A 线程执行的过程中,先把引用地址发布,但是还未进行初始化的情况,这是如果B线程来访问,他会看见已经赋值好的实例地址,但是内容却是空的。
/**
* 静态内部类,使用双重校验锁 (DCL),线程安全【推荐】
*/
public static class Singleton7 {
private volatile static Singleton7 instance = null;
private Singleton7() {
}
public static Singleton7 getInstance() {
if (instance == null) {
// 类级别的 锁
synchronized (Singleton7.class) {
//不进行判断可能会导致出现多个实例
if (instance == null) {
instance = new Singleton7();
}
}
}
return instance;
}
}
3. 懒汉式之-----静态内部类(暂略)
/**
* 单例模式,使用静态内部类,线程安全【推荐】
* <p>
* 可以做到:延迟加载
* <p>
* 原因:
* SingletonHolder 是一个静态内部类,
* 当 外部类 IdGenerator 被加载的时候,并不会创建 SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,
* 这个时候才会创建 instance。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。
*/
public static class Singleton5 {
private final static class SingletonHolder {
private static final Singleton5 INSTANCE
= new Singleton5();
}
private Singleton5() {
}
public static Singleton5 getInstance() {
return SingletonHolder.INSTANCE;
}
}
4.饿汉(线程安全【推荐】
)
提前创建一个单例能有多耗时耗力呐?
public class HungryInstance {
/**
* 单例模式,饿汉式,变种,线程安全 【推荐】
*/
public static class Singleton4 {
private static Singleton4 instance = null;
// static 过程保证线程安全
static {
instance = new Singleton4();
}
private Singleton4() {
}
public static Singleton4 getInstance() {
return instance;
}
}
/**
* 单例模式,饿汉式,线程安全 【推荐】
*/
public static class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
public static void main(String[] args) {
System.out.println(Singleton.getInstance() == Singleton.getInstance());
System.out.println(Singleton5.getInstance() == Singleton5.getInstance());
System.out.println(Singleton6.INSTANCE == Singleton6.INSTANCE);
}
}
# 5.枚举实现(线程安全【最推荐】
)
/**
* 使用 枚举 方式,线程安全,还可以避免 反射更改单例【最推荐】
*/
public enum Singleton6 {
INSTANCE;
public void whateverMethod() {
}
}
其他
单例模式中的唯一性:局限在JVM进程中。
如何实现线程唯一的单例?
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();
}
}
如何实现集群环境下的单例?
需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。
为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,还需要显式地将对象从内存中删除,并且释放对对象的加锁。
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();
}
}
// IdGenerator使用举例
IdGenerator idGeneator = IdGenerator.getInstance();
long id = idGenerator.getId();
IdGenerator.freeInstance();
如何实现一个多例模式
如何实现一个多例模式? 即允许:创建固定的 k 个实例
public class BackendServer {
private long serverNo;
private String serverAddress;
private static final int SERVER_COUNT = 3;
private static final Map<Long, BackendServer> serverInstances = new HashMap<>();
static {
serverInstances.put(1L, new BackendServer(1L, "192.134.22.138:8080"));
serverInstances.put(2L, new BackendServer(2L, "192.134.22.139:8080"));
serverInstances.put(3L, new BackendServer(3L, "192.134.22.140:8080"));
}
private BackendServer(long serverNo, String serverAddress) {
this.serverNo = serverNo;
this.serverAddress = serverAddress;
}
public BackendServer getInstance(long serverNo) {
return serverInstances.get(serverNo);
}
public BackendServer getRandomInstance() {
Random r = new Random();
int no = r.nextInt(SERVER_COUNT) + 1;
return serverInstances.get(no);
}
}
按照类型创建,同一类型的只能创建一个对象,不同类型的可以创建多个对象
public class Logger {
private static final ConcurrentHashMap instances
= new ConcurrentHashMap<>();
private Logger() {
}
public static Logger getInstance(String loggerName) {
instances.putIfAbsent(loggerName, new Logger());
return instances.get(loggerName);
}
public void log() {
//...
}
}
//l1==l2, l1!=l3
Logger l1 = Logger.getInstance("User.class");
Logger l2 = Logger.getInstance("User.class");
Logger l3 = Logger.getInstance("Order.class");