设计模式之单例模式

网上有那么多的资料,我们为什么还要费时费力的去写博客呢?我想应该是要写出自己本身对一个问题的看法,否则真的没必要写,

直接转载别人的就可以了。以前总是有点应付的感觉,对自己的这样的行为真的很惭愧。话不多说,直入正题。

单例模式也是被用的次数最多的模式之一,接下来我们说下单例模式最简单的一种写法:

class Singleton1{
	
	private static Singleton1 singleton1=new Singleton1();
	
	private Singleton1(){
		
	}
	
	public static Singleton1 getInstance(){
		return singleton1;
	}
	
}

这并不是单例模式的标准形式,可以根据自己需要来写。从上面不难看出:

1:一个私有的方法,防止对象再次被实例化。

2:一个静态的方法返回一个被实例化的对象,用来执行一些方法。注意这个方法是静态的哦,否则就需要实例化去执行这个方法,而

单例模式是不能再次实例化的。

为什么叫单例模式呢?

这大概是因为在程序运行的过程中,保证只有一个实例存在。

上述形式虽然完成了我们的需要,但从另一个角度上来说,如果我们访问了这个类的任何一个其他的静态域,那么会出现什么后果呢?

这就会导致所有的静态的变量被加载,这可能导致我们也许从来就没有使用过这个变量,白白浪费资源。

既然可能白白的浪费资源,那么就对它稍作调整。

class Singleton1{
	
	private static Singleton1 singleton1=null;
	
	private Singleton1(){
		
	}
	
	public static Singleton1 getInstance(){
		
		if(singleton1==null){
			singleton1=new Singleton1();
		}		
		
		return singleton1;
	}
	
}

也许你觉得这已经很完美了,但是,对于考虑并发的情况下,这种情况还真的实用么?

首先我们需要说的是java实例化一个对象的步骤:

1:分配内存空间

2:初始化构造器

3:将内存地址指向分配的内存空间

这是一个jvm初始化对象的过程,2,3的部分的顺序是可能会改变的,毕竟设计jvm的时候会考虑到优化,而优化算法是会对这个过程的

指令进行理论上的改变,以达到期望的效率。

这就让我们想到上面的问题?

假如有100个人进行登录操作,调用配置文件读取,也就是对单例的访问,我们能够保证只有一个实例被创建了么?

下面就简单说一下:

for(int i=0;i<100;i++){
    new Runnable() {                
        public void run() {
            Singleton1.getInstance();
        }
    };                
}

现在我们第一个线程来了,检测到singleton1为null,进行创建类创建实例,然而在创建实例未完成之前,第二个线程来了,然而他依然检测到singleton1为null值

在这种情况下,我们不能够保证只有一个实例被创建出来,那么我们的单例就出错了。

既然这样写不是线程安全的,那么我们将要面对这种情况怎么办?

首先,大家想到的是进行加锁,进行同步代码块,毕竟锁机制本来的目的是为了同步代码而来的。

然后我们就有了另一种写法:

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

这种方法的弊端显而易见,我们要是有有1000个线程,不能老是等一个线程全部判断完吧,这样我们的效率会低到一个无法接受的地步吧

所以这种写法我们不能接受,我们就有了另一种写法:

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

第一次判断是否为null值是可以理解的,但是为什么还要判断null值呢?

这就要看我们的过程呢?我们的线程1,2,3,线程1,2都执行到了第一次判断null之后,接着线程1去实例化对象,实例化完了,线程2进去了,然而线程2不知道

我们已经实例化完了,他会一股脑再进行另一次实例化,而线程3反而可以直接判断不为null,直接跳过加锁的同步化代码,这样我们即提升了效率又避免了错误,这样很好

我也是这么认为为的,但是我们前面说了jvm的实例化对象的过程,在这种情况下,我们真的能保证不会有问题么?

如果jvm的实例化对象和1,2,3的步骤顺序一致,那么这样是对的,但是我们不能保证是这个顺序,也许顺序变成了1,3,2,如果顺序是1,3,2的情况,那么会导致

什么样的状况呢?

这可能导致我们提前将栈中的地址指向了堆中的对象,就会导致等待的线程2误认为线程1已经实例化对象完成,然而并没有进行初始化构造的过程,直接导致了我们的线程2返回的是没有初始化构造的对象。这时我们会问,难道就没有理论上没有问题的单例么?这也太操蛋了吧。

当然有了,如果没有,我们的单例模式还有意义?

下面就说下我们的最终模式:

public class Singleton {
	 
	 private Singleton(){}
	 
	 public static Singleton getInstance(){

		 return SingletonInstance.singleton;
		 
	 }
	 
	 private static class SingletonInstance{
	  
	     protected static Singleton singleton = new Singleton();
	  
	 }
	 
}

利用内部类的构造,我们能规避上述的问题,既能解决了效率问题,又能同步代码问题,那么难道我们用加锁机制不能完成最终操作么?

这个当然是能的,我们能考虑到的问题,java的开发者当然能考虑到,在jdk1.5之后就有了volatile,这个功能也相当于取消了jvm的实例化时指令优化取消,只要我们在对象上加上这个关键字,那么我们的实例化操作的1,2,3就会按照这个顺序被强迫执行完,不过在jdk1.5以后才有用。

这篇文章想了很久,也参考了很多资料,对我的许多理解帮助甚大,希望对正在学习的人有帮助。当然有什么错误也请和我说,技术的路上达者为师。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值