码迷,mamicode.com
首页 > 其他好文 > 详细

单例模式

时间:2015-05-09 08:57:47      阅读:111      评论:0      收藏:0      [点我收藏+]

标签:设计模式



在面向对象程序设计中,对所有事物、事件的描述都是通过类, 或者更确切的说是由类的实例—对象来体现的。一个基于面向对象的程序,小到helloword控制台应用程序,大到复杂的金融信息管理系统,都离不开对象的创建和对象之间的交互。对象的创建也是一件值得深入思考的问题。在经典的设计模式中,与对象创建相关的就有工厂方法,抽象工厂,单例和原型模式,每一个模式都有自己特别的应用场景,熟练掌握和应用每种模式对提高编程 能力大有帮助。

一、如何创建对象,即如何实例化类

大部分情况下,通过访问类的构造函数就能得到类的实例,但前提是构造函数被暴露出来,能够被其他对象访问到。

还有利用反序列化,反射等机制来创建对象。

 

二、如何保证一个类只有一个实例

在很多情况下会要求一个类只能有一个实例,如何实现?

我们知道每次调用构造函数就能得到类的一个实例, 如果类构造函数不能在类外部所访问,其他对象就不能通过调用构造函数来实例化这个类。

我们知道没有类的实例是无法访问类的成员方法,但是可以访问类的静态方法(也称作类方法)。因此可以通过类方法类获取类的实例。

public class SimpleSingleton {
    private SimpleSingleton(){

    }
	
    public static SimpleSingleton getInstance(){
	return new SimpleSingleton();		
    }
}

这样通过调用SimpleSingleton.getInstance()就能得到SimpleSingleton的实例,但是不能保证类只有一个实例,因此不能每次调用getInstance类方法时就直接调用构造函数。

可以使用一个静态变量保存类的实例,在第一次调用getInstance方法时,调用构造函数初始化静态变量,以后每次直接获取静态变量的值。

public class SimpleSingleton {
    private static SimpleSingleton SimpleSingletonInstance = null;

    private SimpleSingleton(){
    }
	
    public static SimpleSingleton getInstance(){
	if(SimpleSingletonInstance == null){
            SimpleSingletonInstance = new SimpleSingleton();
      	}			
	return SimpleSingletonInstance;
    }
}

这就实现了一个经典的单例,其特点是:

1) 类的构造函数被保护起来,无法在类的外部直接调用

2) 有一个静态成员变量,保存类实例的引用

3)有一个静态方法,获取类的唯一实例

能保证任何时候调用静态方法getInstance()时获取到的实例是相同的吗?在单线程情况下,该方法获取的实例是同一个,不会有任何问题,但是在多

线程情况下两个纯种就有可能取到不同的实例。如第一个线程在调用getInstance时,进入了if分支,并且调用了构造函数,但由于构造函数需要花费

较长的时间,时间片到了构造函数还没有结束,操作系统调度第二个线程执行,同样也会进行if判断,这时静态变量SimpleSingletonInstance还是

null,也会调用构造函数,导致两个线程各自创建一个实例。

public class SimpleSingleton {
    private static int instanceCount = 0;
    private static SimpleSingleton SimpleSingletonInstance = null;
	
    private SimpleSingleton(){
	instanceCount++;
    }
	
    public static SimpleSingleton getInstance() throws InterruptedException{
	if(SimpleSingletonInstance ==null){
            Thread.sleep(10);
            SimpleSingletonInstance = new SimpleSingleton();
	}
	System.out.println(instanceCount);
	return SimpleSingletonInstance;
    }
}
	
public class TestSimpleSingleton {
    public static void main(String[] args) {
	TestSimpleSingleton TestSingleton = new TestSimpleSingleton();
      	for(int i = 1; i < 10; i++){
	   Thread t = new Thread(TestSingleton.new MulThread());
	   t.start();
	}
    }
	
    class MulThread implements Runnable{
	@Override
	public void run() {
            try {
		SimpleSingleton singleton = SimpleSingleton.getInstance();
	    } catch (InterruptedException e) {
		System.out.println("Sleep method Exception!");
	    }			
	}	
    }
}

测试结果:1,2,3,4,5,6,7,8,9

通常有两种解决办法:1)加锁;2)预加载,也称饿汉式

1)加锁

public static SimpleSingleton getInstance() throws InterruptedException{
        synchronized(SimpleSingleton.class){
	    if(SimpleSingletonInstance ==null){
		Thread.sleep(10);
		SimpleSingletonInstance = new SimpleSingleton();
	    }
        }
	System.out.println(instanceCount);
	return SimpleSingletonInstance;
    }

    这样可行吗,当然可行,但是太浪费资源了,每次调用都加锁,相当于第次只允许一个线程调用该方法,如果该线程没有调用完毕,其他线程是无

法调用该方法的。多线程返回不同野合的情况其实只发生在第一次创建该实例时,也就是静态实例变量还为赋值,因此只需要在

SimpleSingletonInstance为null时需要加锁,将代码改为如下:


public static SimpleSingleton getInstance() throws InterruptedException{
        if(SimpleSingletonInstance ==null){
            Thread.sleep(10);
	    synchronized(SimpleSingleton.class){				
		SimpleSingletonInstance = new SimpleSingleton();
	    }
	}
	System.out.println(instanceCount);
	return SimpleSingletonInstance;
    }

但是还是有可能两个线程得到不同的实例,第一个线程刚执行完if就挂起了,第二个线程调用getInstance方法时也能调用构造函数,第一个线程再次被调度时也会调用构造函数,导致两个线程得到不同的实例。

解决的办法是在同步代码块中再次判断实例变量是否被赋值。


public static SimpleSingleton getInstance() throws InterruptedException{
    if(SimpleSingletonInstance ==null){
        Thread.sleep(10);
        synchronized(SimpleSingleton.class){
	    if(SimpleSingletonInstance ==null){
		SimpleSingletonInstance = new SimpleSingleton();
            }
	}
    }
    System.out.println(instanceCount);
    return SimpleSingletonInstance;
}

由于在整个函数处理过程中判断了两次静态变量是否被赋值的操作,该方法也被称为同步二次校验法。

同步是解决多线程问题最常用的方式,但是加锁会引起性能的损失,在上述同步代码中性能损失可以忽略不计。

测试结果输出:1,1,1,1,1,1,1,1,1

2)预加载

    private static SimpleSingleton SimpleSingletonInstance = new SimpleSingleton();

利用类的静态变量在类加载时初始化来保证构造函数仅被调用一次,而且能避免同步带来的性能损耗。但是,如果类的构造函数需要花费大量的时间,而类的实例变量不一定被访问到,这就消费了大量的系统资源。

测试结果输出:1,1,1,1,1,1,1,1,1

 

单例总结:

1)构造函数是私有的,类外部无法访问

2)静态变量保存类的实例

3)静态方法返回实例

4)保存类的实例时可以采用预加载和同步二次校验两种方式解决,如果类的实例不一定被访问到,应用使用同步二次校验的方式。



单例模式

标签:设计模式

原文地址:http://blog.csdn.net/musa875643dn/article/details/45585069

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!