前记
昨天学妹抱怨专业课中的java 23个设计模式。她问:23个设计模式越看越扎心,尤其是单例模式,有没有什么破解之法。我告诉她java 23个设计模式看上去多,要挑知识重点并结合程序实例来记忆,尤其是学习之后自己动手写一个设计模式,很容易能掌握。我给她讲解了如何去学习单例模式。最后她哈哈大笑,“我懂了”。
对于java 23个设计模式,每个刚接触的人都可能觉得设计模式太多了而萌生不想学习的念头,特别是第一次接触的时候。于是我写这篇文章,用最通俗易懂的语言讲解单例模式,帮助大家理解。
文章的标题只是噱头,作为热爱交流技术的学习者,我们应该脚踏实地,所以我会保证文章的内容都是干货!
适用场景
一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化。比如windows系统的回收站是单例模式,打开一个回收站,关闭后再打开一个回收站,前后两个回收站是一样的。
饿汉式
主动创建实例的私有静态引用。调用前单例已经存在。
优点:简单方便,线程安全
缺点:如果没有调用该实例对象,则造成内存资源浪费
public class Singleton1 {//饿汉模式,天然线程安全// 指向自己实例的私有静态引用,主动创建private static Singleton1 singleton1 = new Singleton1();// 私有的构造方法private Singleton1(){}// 以自己实例为返回值的静态的公有方法,静态工厂方法public static Singleton1 getSingleton1(){return singleton1;}}
懒汉式
调用时创建实例对象。只有调用时,才去初始化单例。
优点:简单方便,不使用时不创建对象
缺点:线程不安全,多个线程的情况下,如果一个线程符合判断条件,另一个线程也进入条件,可能产生多个实例对象
public class Singleton2 {//懒汉式private static Singleton2 singleton2;private Singleton2(){}public static Singleton2 getSingleton2(){if(singleton2 == null){singleton2 = new Singleton2();}return singleton2;}}
单锁模式
使用sychronized修饰getSingleton()方法
优点:线程安全
缺点:粗粒度,对程序的性能影响大
public class Singleton4 {private static Singleton4 singleton4 = null;private Singleton4(){}public static synchronized Singleton4 getSingleton4() {if(singleton4 == null){singleton4 = new Singleton4();}return singleton4;}}
双重加锁判断
创建一个静态锁,并且使用两个if判断
优点:线程安全,保证了程序的性能,并且节约了资源
public class Singleton3 {//程序运行时创建一个静态只读的进程辅助锁private static Object syncRoot = new Object();private static Singleton3 singleton3;private Singleton3(){}public static Singleton3 getSingleton3() {if(singleton3 == null){//先判断是否存在synchronized (syncRoot){if(singleton3 == null){singleton3 = new Singleton3();}}}return singleton3;}}
静态内部类
优点:既实现了线程安全,又避免了同步机制带来的性能影响
public class Singleton6 {private static class LazyHolder{private static final Singleton6 singleton6 = new Singleton6();}private Singleton6(){}public static final Singleton6 getSingleton6(){return LazyHolder.singleton6;}}
登记式
维护了一组单例类的实例,将这些实例放在一个Map中,对于已经登记过的实例,则从Map直接返回,没有登记过的则先登记,后返回。
import java.util.HashMap;import java.util.Map;public class Singleton5 {private static Map<String,Singleton5>map = new HashMap<>();static {Singleton5 singleton5 = new Singleton5();map.put(singleton5.getClass().getName(),singleton5);}private Singleton5(){}public static Singleton5 getSingleton5(String name){if(name == null){name = Singleton5.class.getName();}if(map.get(name) == null){try {map.put(name,(Singleton5) Class.forName(name).newInstance());} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}return map.get(name);}}
单例模式优缺点
优点
节省内存空间,在内存中只有一个对象
减少了系统的性能开销
例如一个对象需要许多资源时,比如读取配置,产生其他依赖的对象。在应用启动时直接产生一个实例对象。 避免对资源的多重占用 比如写文件,因为只有一个实例,避免对同一个文件同时写操作。 可以在系统设置全局的访问点,优化和共享资源访问。 例如设计一个单例,负责系统内所有数据表的处理。
缺点
单例模式一般没有接口,扩展比较困难,除非修改代码。
单例对象如果持有Context,那么很容易引发内存泄露。
总结
单例模式是java 23种设计模式之一。单例模式适用于对象需要被频繁地创建销毁并且性能无法优化的场景。
本文介绍了单例模式中的饿汉式、懒汉式和登记式。其中饿汉式与懒汉式需要重点学习和理解。饿汉式是天然线程安全,而懒汉式线程不安全,所以引出了懒汉式的单锁模式、双重加锁判断和静态内部类。