设计模式之单例模式(Singleton)
单例模式:确保一个类只有一个实例,并提供一个全局访问点。
?
要想保证一个类只有一个实例,我们不能将构造方法暴露出去,否则调用方就可能通过你提供的构造方法去实例化该类的实例,这样我们就无法保证该类只有一个实例了。因此,我们不能给类的构造方法赋予public的访问权限。
?
单例模式的实例化分为两种:急切实例化和延迟实例化
?
急切实例化:依赖JVM在加载这个类时马上创建此唯一的单件实例,通常表现为一个静态引用。如果程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,我们可以采取急切实例化的方式创建单件。
public class Product {//静态成员——将在JVM加载该类的时候就完成对象的实例化private static Product product = new Product();private Product(){}public static Product newInstance(){//始终返回被加载时创建的静态成员实例return product;}}?
急切实例化单例模式UML图:
延迟实例化:只在真正需要该类的实例的时候才实例化。通常在创建和运行时负担比较重的时候选用此种方案。
public class Product {//静态成员——之前先不去实例化对象private static Product product;private Product(){}public static Product newInstance(){//只有在需要该类的时候调用此方法时才去完成实例化if(null == product){product = new Product();}return product;}}?
延迟实例化单例模式UML图:
延迟实例化所面临的线程安全问题:
很多人在使用延迟实例化单例模式时都没有考虑线程安全问题。我们看下面一段代码:
/** * 延迟实例化单例模式 */public class Product {private static Product product;private Product(){}public static Product newInstance() throws Throwable{if(null == product){Thread.sleep(5000);//这时可能会有两个线程同时进入到这里product = new Product();//之后可能会创建两个Product类的实例}return product;}}
?上段代码有时也可能只会创建一个实例,这取决哪个线程抢占到执行权,但不排除创建两个实例的可能性,有兴趣的朋友可以自己多尝试几次,或者借助调试的手段手动切换两个线程的执行顺序,就会出现创建两个实例的情景。既然如此,我们以后就不要像上面的代码这样使用延时实例化。
?
如何解决延迟实例化所面临的线程安全问题:
方法一:使用synchronized,如下所示:
/** * 延迟实例化单例模式 */public class Product {private static Product product;private Product(){}public static synchronized Product newInstance() throws Throwable{if(null == product){product = new Product();}return product;}}?
上面的代码虽然能解决线程安全问题,但是每次调用newInstance方法时都会被同步,无疑会带来性能损耗,你必须知道同步一个方法可能造成程序执行效率下降100倍。如果你可以接受这样的额外损耗,你大可可以这样来用(即简单又有效),如果你可能需要频繁的调用这个同步方法,又无法接受这样的性能损失,可能就得想其他的办法啦。
?
方法二:使用“双重检查加锁”,如下所示:
/** * 延迟实例化单例模式 */public class Product {private volatile static Product product;//使用volatileprivate Product(){}public static Product newInstance() throws Throwable{if(null == product){synchronized(Product.class){//保护起来if(null == product){//之后再次检查product = new Product();}}}return product;}}?
这样做比直接使用同步方法带来的损耗要低很多。如果你不想使用synchronized,也可以使用阻塞队列,JDK也是这么推荐的。
?
方法三:使用急切实例化,当然之前介绍了急切实例化也有他的缺点,如果你能接受的话,这也是个简单有效的方案。
?
单例模式请注意多个ClassLoder
每个类加载器都定义了一个名空间,如果有两个以上的类加载器,不同的类加载器可能会加载同一个类,从整个程序来看,同一个类被加载多次,如果这样的事情发生在单件上,就会产生多个实例并存的“单件”,所以需要引起注意。
?
参考资料:
Head First 设计模式 (中国电力出版社)
?
?
?
?
?
?
?
?