单例模式

创建型设计模式,确保一个类只有一个实例,并提供该实例的全局访问点。
使用一个私有构造函数,一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
实现

1、饿汉式-线程安全

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

优点:直接实例化instance不会产生线程安全问题,最简单、最经典,绝对线程安全。
缺点:直接实例化丢失了延迟实例化带来的节约资源的好处。

2、懒汉式-线程不安全

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

优点:私有静态变量instance被延迟实例化,从而节约资源。
缺点:此实现在多线程环境下不安全,如果多个线程同时进入if(instance == null),且此时instance为null,那么会有多个线程执行instance = new Singleton();语句,将导致instance多次实例化。

3、懒汉式-线程安全

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

优点:只需要对getInstance()方法加锁,就可以保证一个时间点只能有一个线程进入该方法,从而避免了多次实例化instance。
缺点:当一个线程进入该方法,其它试图进入该方法的线程都必须等待,即使instance已经实例化。这将导致线程阻塞时间过长,因此有性能问题,不推荐使用。

4、双重校验锁-线程安全

public class Singleton{
	private volatile static Singleton instance;
	private Singleton(){
	}
	public static Singleton getInstance(){
		if(instance == null){
			synchronized(Singleton.class){
				if(instance==null){
					instance = new Singelton();
				}
			}
		}
		return instance;
	}
}

instance只需被实例化一次,之后就可以直接使用。加锁操作只需对实例化那部分代码加锁,只有当instance没有被实例化,才需要进行加锁。
双重校验锁先判断instance是否已经实例化,没有实例化才对实例化语句加锁。

if(instance == null){
		synchronized(Singleton.class){
				instance = new Singelton();
		}
}

考虑上述实现,只使用一个if语句。在instance==null的情况下,如果两个线程都进入if语句块,虽然if语句块内有加锁操作,但两个线程都会执行instance = new Singelton();这条语句,只是先后问题,那么就会进行两次实例化。因此必须使用双重校验锁,也就是需要使用两个if语句;第一个if语句避免instance已经被实例化之后的加锁操作,而第二if语句进行了加锁,所以只有一个线程进入,就不会出现instance == null时两个线程同时进行实例化操作。
instance采用volatile关键字修饰也很有必要,instance=new Singleton();这段代码其实分三步执行:

  • 1.为instance分配内存空间
  • 2.初始化instance
  • 3.将instance指向分配的内存地址
    但是由于JVM具有指令重排的特性,执行顺序有可能变成1>3>2。指令重排在单线程环境下不会出问题,但在多线程环境下会导致一个线程获得还没初始化的实例。如,线程t1执行了1和3,此时t2调用getInstance()后发现instance不为空,因此返回instance,但此时instance还未被初始化。使用volatile可以禁止JVM的指令重排,保证在多线程环境下也能正常运行。

5、静态内部类实现

public class Singleton{
	private Singleton(){
	}
	private static class SingletonHelper{
		private static final Singleton instance = new Singleton();
	}
	public static Singleton getInstance(){
		return SingletonHelper.instance;
	}
}

当Singleton类被加载,静态内部类SingletonHelper没有被加载进内存。只有当调用getInstance()方法从而触发SingletonHelper.instance时SingletonHelper才会被加载,此时初始化instance实例,并且JVM能保证instance只被实例化一次。这种方法不仅具有延迟加载的好处,而且由JVM提供了对线程安全的支持。

6、枚举实现

在上述实现中,反射可以通过setAccessible()方法将私有构造函数的访问级别设置为public,然后调用构造函数从而实现实例化,也就是反射攻击,需要在构造函数中添加防止多次实例化的代码。

public enum Singleton{
INSTANCE;
private String objName;
public String getObjName(){
	return objName;
}
public void setObjName(String objNmae){
	this.objName = objName;
}
}

此实现可以防止反射攻击,它由JVM保证只会实例化一次。该实现无偿地提供了序列化机制,在多次序列化和反序列化之后,不会得到多个实例。而其它实现需要使用transient修饰所有字段,并且实现序列化和反序列化方法。

  • 枚举类的特性
    枚举类可以实现接口,但不能继承接口,也不能被继承;
    枚举类是final的,所以不能继承;
    枚举类的构造方法是私有的;
    枚举成员是静态,final和public的;
    枚举成员是枚举类的实例。
  • 枚举反编译源码探求
    自定义的枚举类会自动继承java.lang.Enum类,实现了序列化接口
    每个成员变量都会被转换为public static final的枚举类型的实例
    自动添加private的构造函数

7、注册式单例

spring中的单例采用此方式。

public class Singleton{
//构建采用ConcurrentHashMap,用于充当缓存注册表
private static final Map<String,Object> singletonMap = new ConcurrentHashMap<>();
//静态代码块只加载一次
static{
	//实例化bean
	Singleton instance = new Singleton();
	//并注册到注册表,key为类的完全限定名,value为实例化对象。
	singletonMap.put(instance.getClass().getName(),instance);
}
private Singleton(){
}
public static Singleton getInstance(String beanName){
	if(beanName == null){
		return null;
	}
	//从注册表中获取,如果没有直接创建
	if(singletonMap.get(beanName)==null){
		try{
			//如果为空,通过反射进行实例化
			singletonMap.put(beanName,Class.forName(beanName).newInstance());
		}catch(Exception e){
			e,printStackTrace();
		}
	}
	//从缓存表中回去,如果缓存命中直接返回
	return (Sigleton)singletonMap.get(beanName);
}
}

采用ConcurrentHashMap是出于线程安全的考虑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值