设计模式之单例模式
单例模式:确保一个类只有一个实例,并提供一个全局的访问点。
在单例模式下,当需要返回单个实例时,通过单件类获取是唯一的途径。
情景:小明家只有一辆车,车在某一个时刻,只有一个状态,要么前进,要么后退,也就是倒车。
案例代码:
在正规的单例模式中,单例类需要提供似有的构造方法,通过共有的全局访问点。在本测试代码中为了比较差异,对单例模式稍作改动。
单例模式只允许创建一个对象,为了检查是不是一个对象,我把对象的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中实现单件模式需要似有构造器和一个静态方法和一个静态变量。在使用单件模式的第一种方案时要处理好多线程引发的问题。