标签:设计模式
在面向对象程序设计中,对所有事物、事件的描述都是通过类, 或者更确切的说是由类的实例—对象来体现的。一个基于面向对象的程序,小到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