手写一个单例模式

直接上正题


(不推荐)懒汉式写法(线程不安全)

下面写法在单线程可以使用;多线程可能会有线程安全问题。
比如有线程A和B,A运行到(1)处,转交CPU的使用权给B,B线程会创建一个实例,等到A拿回控制权,完全不知道B已经创建了实例,会再次运行new Singleton(),创建多余的实例,违反单例模式的定义。

public class Singleton{

	private static Singleton singleton;
	private Singleton(){
	}
	
	public static Singleton getInstance(){
		if(singleton==null){//(1)
			singleton=new Singleton();
		}
		return singleton;
	}
}

(不推荐)懒汉式写法(线程安全)

这种写法使用同步保证线程安全,但是大部分业务场景是不需要同步的,造成了不必要的同步开销,不建议这种方式。

public class Singleton {

	private static Singleton singleton;
	private Singleton() {
	}
	
	public static synchronized Singleton getInstance() {
		if(singleton==null) {
			singleton=new Singleton();
		}
		return singleton;
	}
}

(推荐)懒汉式写法(双重校验)

使用双重校验的方式,通过加锁使只有一个进程进入创建实例。
还用volative防止指令重排和实现对其他线程可见性

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;
	}
}

(不推荐)饿汉式写法

这种方式在类加载时就完成实例的初始化,所以类加载比较慢,但是正是由于使用类加载机制也就避免了线程安全的问题。
如果是静态资源导致了类加载,这时可能并不需要实例化,也就是没有实现懒加载的效果。

public class Singleton {
	
	private static Singleton singleton=new Singleton();
	private Singleton() {}
	
	public static Singleton getInstance() {
		return singleton;
	}
}

(推荐)静态内部类写法

在类加载的时候并不会创建实例,只有在第一次调用getInstance()才会创建实例,实现懒加载。
至于线程安全,可以这么理解,JVM会保证一个类加载器在多线程的环境下被正确的加锁、同步。当多个线程同时去初始化一个类,只有一个线程可以执行类构造器,其他线程会阻塞,直到活动线程执行完类构造器。
要注意的是在活动线程执行完类构造器后,其他的线程也不会再执行类初始化,因为在同一个类加载器中,一个类型只会被初始化一次,这就保证了线程安全。
想要深入理解类加载机制、静态内部类的线程安全;点击这里

public class Singleton{
	
	private static class SingletonHolder{
		private static final Singleton INSTANCE=new Singleton();
	}
	private Singleton() {}

	public static final Singleton getInstance() {
		return SingletonHolder.INSTANCE;
	}
}

(推荐)枚举写法

这种方法是effective java作者提倡,不仅能避免多线程同步问题还能防止反序列化,并且在任何情况下都是单例;枚举单例的优点就是简单,虽然可读性并不是很高,但是感觉是大趋势。
想要深入理解,点击这里

public enum Singleton{
	INSTANCE;
	public Singleton getInstance() {
		return INSTANCE;
	}
}

个人比较喜欢这种方式,上面的可能比较抽象,放一下我常用的例子 User.java

public class User {
	
	//私有化构造函数
	private User() {}
	
	//定义一个静态枚举类
	static enum SingletonEnum{
		//创建一个枚举对象,该对象天生为单例
		INSTANCE;
		private User user;
		
		//私有化枚举的构造函数
		private SingletonEnum() {
			User user=new User();
		}
		public User getUser() {
			return user;
		}
	}
	
	//对外暴露一个获取User对象的静态方法
	public static User getUser() {
		return SingletonEnum.INSTANCE.getUser();
	}
}

(不推荐)容器的写法

容器写法可以将多种的单例进行统一管理,在使用时根据key获取对应实例对象。
这种方式使用时可以通过统一的接口进行获取操作,但是具体实现是隐藏的,由于hashMap在存值时会有一个key存多个值的情况,就会导致取值时有安全风险,虽然最后获取的值一样但中间会经历值覆盖的过程,具体的可以自行debug查看。
感兴趣的可以 点击这里

public class Singleton{ 
  private static Map<String, Object> singletonMap= new HashMap<String,Object>();
  private Singleton() { 
  }
  public static void putInstance(String key, Object instance) {
        if (key.length()>0 && instance != null) {
            if (!singletonMap.containsKey(key)) {
                singletonMap.put(key, instance);
            }
        }
    }
  public static getInstance(String key) {
    return singletonMap.get(key) ;
  }
}

总结

具体使用哪种方式还是要看项目的实际情况,主要考虑并发的复杂度以及资源损耗的接受程度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值