概念:单例对象的类必须保证只有⼀个实例存在。
适⽤场景: 单例模式只允许创建⼀个对象,因此节省内存,加快对象访问速度,因此对象需要被公⽤的场合适合使⽤,如多个模块使⽤同⼀个数据源连接对象等等。如:
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但⼜经常⽤到的对象。
- 有状态的⼯具类对象。
- 频繁访问数据库或⽂件的对象。
常⻅写法:
public class Singleton {
/**
* 优点:没有线程安全问题,简单
* 缺点:提前初始化会延⻓类加载器加载类的时间;如果不使⽤会浪费内存空间;不能传递参数
*/
private static final Singleton instance = new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return instance;
}
}
饿汉式:在实例化类的时候就会创建对象。
优点:
- 会提前初始化,你调用的时候就不需要再去初始化。性能是好的。
- 没有线程安全问题,简单
缺点:
- 提前初始化会延⻓类加载器加载类的时间;
- 如果不使⽤会浪费内存空间,因为,即使不用它,它也会在那;
- 不能传递参数。
public class Singleton{
/**
* 优点:解决线程安全,延迟初始化( Effective Java推荐写法)
*/
private Singleton(){}
public static Singleton getInstance () {
return Holder.SINGLE_TON;
}
private static class Holder{
private static final Singleton SINGLE_TON = new Singleton();
}
}
懒汉式:延迟初始化的手段
调用它的时候,会去初始化;不调用它的时候,就不初始化。
优点:
解决线程安全,延迟初始化( Effective Java推荐写法)
public class Singleton {
private volatile static Singleton uniqueSingleton;
private Singleton() {
}
public Singleton getInstance() {
if (null == uniqueSingleton) {//第一重检查
synchronized (Singleton.class) {//第一重加锁
if (null == uniqueSingleton) {//第二重检查
uniqueSingleton = new Singleton();//创建对象
}
}
}
return uniqueSingleton;
}
}
双重检查锁原理:
第一次判断为空以后,第一次加锁。
因为加锁不是一个原子操作,在加锁的过程中,uniqueSingleton对象可能已经被初始化掉了,所以加锁完毕以后,还需要去判断一下,uniqueSingleton对象是不是为空?
加锁后,进行第二次检验(检查)
如果uniqueSingleton对象为空,进行创建对象。
注意:
volatile
- 会保证可见性
- 不会保证原子性。
具体原理:
加了一个log的前置指令(即内存屏障),会保证屏障后面的指令不会放到屏障之前,也不会把屏障之前的指令放到屏障之后。在执行到屏障的时候,一定会保证屏障之前的指令全执行完了。
进行写操作的时候:
- 有缓存:写操作会导致缓存失效;
- 无缓存:将缓存里的修改,直接写入到缓存里。
volatile
的作用:
阻止指令重排序;
加入
volatile
,会加入一个内存屏障。加入内存屏障后顺序就不能变了。是禁止指令重排序。
保证可见性;
平时操作时,会有一个主内存和一个工作内存的。
如果不加volatile,数据是放在工作内存的,进行操作的时候,需要从工作内存刷到主内存。加了volatile,相当于不用工作内存,直接用主内存。相当于多个线程对一个对象操作的时候,多个线程对结果是可见的。
为什么考虑指令重排序?
volatile指令重排序
在执⾏程序时,为了提供性能,处理器和编译器常常会对指令进⾏重排序,但是不能随意重排序,不是你 想怎么排序就怎么排序,它需要满⾜以下两个条件:
- 在单线程环境下不能改变程序运⾏的结果;
- 存在数据依赖关系的不允许重排序
指令排序
uniqueSingleton = new Singleton();
- 分配内存空间
- 初始化对象
- 将对象指向刚分配的内存空间
但是有些编译器为了性能的原因,可能会将第⼆步和第三步进⾏重排序,顺序就成了:
- 分配内存空间
- 将对象指向刚分配的内存空间
- 初始化对象
现在考虑重排序后,两个线程发⽣了以下调⽤:
Time Thread A Thread B T1 检查到uniqueSingleton为空 T2 获取锁 T3 再次检查到uniqueSingleton为 空 T4 为uniqueSingleton分配内存空 间 T5 将uniqueSingleton指向内存空 间 T6 检查到uniqueSingleton不为空 T7 访问uniqueSingleton(此时对 象还未完成初始化) T8 初始化uniqueSingleton 在这种情况下,T7时刻线程B对uniqueSingleton的访问,访问的是⼀个
初始化未完成
的对象。使⽤了
volatile
关键字后,重排序被禁⽌
,所有的写(write)操作都将发⽣在读(read)操作之前。
单例模式的破坏
Singleton sc1 = Singleton.getInstance();
Singleton sc2 = Singleton.getInstance();
System.out.println(sc1); // sc1,sc2是同⼀个对象
System.out.println(sc2);
/*通过反射的⽅式直接调⽤私有构造器*/
Class clazz = (Class) Class.forName("com.le arn.example.Singleton");
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true); // 跳过权限检查
Singleton sc3 = c.newInstance();
Singleton sc4 = c.newInstance();
System.out.println("通过反射的⽅式获取的对象sc3:" + sc3); // sc3,sc 4不是同⼀个对象
System.out.println("通过反射的⽅式获取的对象sc4:" + sc4);
//以上代码是说明单例是可以被破坏的
//防⽌反射获取多个对象的漏洞 (即防止单例被破坏的解决办法。)
private Singleton() {
if (null != SingletonClassInstance.instance)
throw new RuntimeException();
}
spring中bean的单例
a. 当多个⽤户同时请求⼀个服务时,容器会给每⼀个请求分配⼀个线程,这时多个线程会并发执⾏该请求对应的业务逻辑(成员⽅法)。此时就要注意了,如果该处理逻辑中有对单例状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。
有状态就是有数据存储功能
。有状态对象(Stateful Bean)就是有实例变量的对象
,可以保存数据,是⾮线程安全的
。在不同⽅法调⽤间不保留任何状态。
⽆状态就是⼀次操作,不能保存数据
。⽆状态对象(Stateless Bean),就是没有实例变量的对象 . 不能保存数据,是不变类,是线程安全的。
b. 实现
public abstract class AbstractBeanFactory implements Configurable
BeanFactory{
/**
* 充当了Bean实例的缓存,实现⽅式和单例注册表相同
*/
private final Map singletonCache=new HashMap();
public Object getBean(String name)throws BeansException{
return getBean(name,null,null);
}
...
public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{
//对传⼊的Bean name稍做处理,防⽌传⼊的Bean name名有⾮法字符(或则做转码)
String beanName=transformedBeanName(name);
Object bean=null;
//⼿⼯检测单例注册表
Object sharedInstance=null;
//使⽤了代码锁定同步块,原理和同步⽅法相似,但是这种写法效率更⾼
synchronized(this.singletonCache){
sharedInstance=this.singletonCache.get(beanName);
}
if(sharedInstance!=null){
...
//返回合适的缓存Bean实例
bean=getObjectForSharedInstance(name,sharedInstance);
}else{...
//取得Bean的定义
RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);
...
//根据Bean定义判断,此判断依据通常来⾃于组件配置⽂件的单例属性开关
//<bean id="date" class="java.util.Date" scope="singleton"/>
//如果是单例,做如下处理
if(mergedBeanDefinition.isSingleton()){
synchronized(this.singletonCache){//再次检测单例注册表
sharedInstance=this.singletonCache.get(beanName);
if(sharedInstance==null){
...
try {
//真正创建Bean实例
sharedInstance=createBean(beanName,mergedBeanDefinition,args);
//向单例注册表注册Bean实例
addSingleton(beanName,sharedInstance);
}catch (Exception ex) {
...
}finally{
...
}
}
}
bean=getObjectForSharedInstance(name,sharedInstance);
}
//如果是⾮单例,即prototpye,每次都要新创建⼀个Bean实例
//<bean id="date" class="java.util.Date" scope="prototype"/>
else{
bean=createBean(beanName,mergedBeanDefinition,args);
}
}
...
return bean;
}
}
编写不易,转载注明出处:https://www.cnblogs.com/lmh15054109/p/13861582.html