设计模式之单例模式

单例模式:确保一个类只有一个实例,并提供一个全局的访问点。

在单例模式下,当需要返回单个实例时,通过单件类获取是唯一的途径。

案例代码下载

情景:小明家只有一辆车,车在某一个时刻,只有一个状态,要么前进,要么后退,也就是倒车。

案例代码:

在正规的单例模式中,单例类需要提供似有的构造方法,通过共有的全局访问点。在本测试代码中为了比较差异,对单例模式稍作改动。

单例模式只允许创建一个对象,为了检查是不是一个对象,我把对象的hashCode打印出来了。

1、Test.java

 

public class Test {
	public static void main(String args[]){
		//小明家只有一辆汽车
		//一个类可以有好多个实例
		Car c_a = new Car();
		Car c_b = new Car();
		
		c_a.driverBackward();
		c_b.driverForward();
		
		System.out.println(c_a);//Car [mName=小明家的汽车, mState=倒车]hashCode373882728
		System.out.println(c_b);//Car [mName=小明家的汽车, mState=向前开]hashCode309858374
		System.out.println("------------------------------");
		//使用单例模式管理汽车
		//虽然创建了两个对象,但同时只有一个引用。
		Car c_1 = SingletonNumOne.getInstance();
		Car c_2 = SingletonNumOne.getInstance();
		
		c_1.driverBackward();
		System.out.println(c_1);//Car [mName=小明家的汽车, mState=倒车]hashCode241990244
		System.out.println(c_2);//Car [mName=小明家的汽车, mState=倒车]hashCode241990244
		System.out.println("-----------------------");
		c_2.driverForward();
		System.out.println(c_1);//Car [mName=小明家的汽车, mState=向前开]hashCode241990244
		System.out.println(c_2);//Car [mName=小明家的汽车, mState=向前开]hashCode241990244
	}
}

2、SingletonNumOne.java//单例对象获取类

 

 

public class SingletonNumOne {
	private static Car car = null;
	
	public static Car getInstance(){
		if(car == null){
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			car = new Car();
		}
		return car;
	}
}

测试结果,我附在打印函数的后面了。

 

这种方式成为”延迟实例化“的方式,创建单例对象,这个模式在大部分状态下都工作正常,但如果在多线程系统中,就不那么正常了。

3、TestMultiThread.java

 

public class TestMultiThread {
	public static void main(String args[]){
		NewThread n1 = new NewThread();
		NewThread n2 = new NewThread();
		new Thread(n1).start();
		new Thread(n2).start();
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Car c1 = n1.getCar();
		Car c2 = n2.getCar();
		
		System.out.println(c1);//Car [mName=小明家的汽车, mState=null]hashCode1667685889
		System.out.println(c2);//Car [mName=小明家的汽车, mState=null]hashCode1987659426
	}
}
class NewThread implements Runnable{
	Car car_1;
	@Override
	public void run() {
		car_1 = SingletonNumOne.getInstance();
	}
	public Car getCar(){
		return car_1;
	}
}

输出结果,我附在打印函数后面了,通过hashcode,我们可以看到,这是两个不同的对象。不是说通过单例模式得到的都是同一个对象吗?这是怎么回事?

 

其实这是java同步并发引起的,试想多个线程,同时运行SingletonNumOne.getInstance()方法,每个线程在任何时刻都可能获取时间片并执行此函数,这就会产生,一个线程正在实例化new Car,另一个线程也运行到这里。

针对这个问题,有三种解决方案。

第一种解决方案:加同步锁

4、SingletonNumOne.java

 

public class SingletonNumOne {
	private static Car car = null;
	
	public static synchronized Car getInstance(){
		if(car == null){
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			car = new Car();
		}
		return car;
	}
}

第二种方式:使用”急切创建模式“得到单例对象

 

5、SingletonNumTwo.java

 

public class SingletonNumTwo {
	private static Car car = new Car();
	
	public static synchronized Car getInstance(){
		return car;
	}
}


第三种:使用双重检查加锁的方式

 

6、SingletonNumThree.java

 

public class SingletonNumThree {
	private volatile static Car mCar;
	
	public static Car getInstance(){
		if(mCar == null){
			synchronized(Car.class){
				if(mCar == null){
					mCar = new Car();
				}
			}
		}
		return mCar;
	}
}


 

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

测试类:

 

7、Test2.java

 


public class Test2 {
	public static void main(String args[]){
		//第一种方式 SingletonNumOne在getInstance方法上加同步锁
		Car car1 = SingletonNumOne.getInstance();
		Car car2 = SingletonNumOne.getInstance();
		
		System.out.println(car1);//Car [mName=小明家的汽车, mState=null]hashCode613481760
		System.out.println(car2);//Car [mName=小明家的汽车, mState=null]hashCode613481760
		System.out.println("---------------------");
		//第二种方法 单例模式”急切“创建法
		Car car3 = SingletonNumTwo.getInstance();
		Car car4 = SingletonNumTwo.getInstance();
		
		System.out.println(car3);//Car [mName=小明家的汽车, mState=null]hashCode1987659426
		System.out.println(car4);//Car [mName=小明家的汽车, mState=null]hashCode1987659426
		System.out.println("---------------------");
		//第三种方法://双重检查加锁
		Car car5 = SingletonNumThree.getInstance();
		Car car6 = SingletonNumThree.getInstance();
		
		System.out.println(car5);//Car [mName=小明家的汽车, mState=null]hashCode2048243029
		System.out.println(car6);//Car [mName=小明家的汽车, mState=null]hashCode2048243029
	}
}

测试结果我附在打印函数后面了。

 

总结:单件模式确保程序中一个类仅有一个实例对象,单件模式提供访问这个单件对象的全局访问点。在java中实现单件模式需要似有构造器和一个静态方法和一个静态变量。在使用单件模式的第一种方案时要处理好多线程引发的问题。


 

posted @ 2013-03-25 14:38  xinyuyuanm  阅读(160)  评论(0编辑  收藏  举报