`

JAVA设计模式之单例模式

阅读更多

好文章值得分享转载博客:http://blog.csdn.net/jason0539

概念:
  Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。
  单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。
  单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。


一、懒汉式单例

    //懒汉式单例类.在第一次调用的时候实例化自己   
    public class Singleton {  
        private Singleton() {}  
        private static Singleton single=null;  
        //静态工厂方法   
        public static Singleton getInstance() {  
             if (single == null) {    
                 single = new Singleton();  
             }    
            return single;  
        }  
    }  
 Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:

 

1、在getInstance方法上加同步

 

    public static synchronized Singleton getInstance() {  
             if (single == null) {    
                 single = new Singleton();  
             }    
            return single;  
    }  

 

2、双重检查锁定

 

    public static Singleton getInstance() {  
            if (singleton == null) {    
                synchronized (Singleton.class) {    
                   if (singleton == null) {    
                      singleton = new Singleton();   
                   }    
                }    
            }    
            return singleton;   
        }  

 

3、静态内部类

 

public class Singleton {    
    private static class LazyHolder {    
       private static final Singleton INSTANCE = new Singleton();    
    }    
    private Singleton (){}    
    public static final Singleton getInstance() {    
       return LazyHolder.INSTANCE;    
    }    
}

 

 

这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。

 

二、饿汉式单例

    //饿汉式单例类.在类初始化时,已经自行实例化   
    public class Singleton1 {  
        private Singleton1() {}  
        private static final Singleton1 single = new Singleton1();  
        //静态工厂方法   
        public static Singleton1 getInstance() {  
            return single;  
        }  
    }  
 饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
 三、登记式单例(可忽略)
    //类似Spring里面的方法,将类名注册,下次从里面直接获取。  
    public class Singleton3 {  
        private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();  
        static{  
            Singleton3 single = new Singleton3();  
            map.put(single.getClass().getName(), single);  
        }  
        //保护的默认构造子  
        protected Singleton3(){}  
        //静态工厂方法,返还此类惟一的实例  
        public static Singleton3 getInstance(String name) {  
            if(name == null) {  
                name = Singleton3.class.getName();  
                System.out.println("name == null"+"--->name="+name);  
            }  
            if(map.get(name) == null) {  
                try {  
                    map.put(name, (Singleton3) Class.forName(name).newInstance());  
                } catch (InstantiationException e) {  
                    e.printStackTrace();  
                } catch (IllegalAccessException e) {  
                    e.printStackTrace();  
                } catch (ClassNotFoundException e) {  
                    e.printStackTrace();  
                }  
            }  
            return map.get(name);  
        }  
        //一个示意性的商业方法  
        public String about() {      
            return "Hello, I am RegSingleton.";      
        }      
        public static void main(String[] args) {  
            Singleton3 single3 = Singleton3.getInstance(null);  
            System.out.println(single3.about());  
        }  
    }  
 登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。 

这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。

 

饿汉式和懒汉式区别

从名字上来说,饿汉和懒汉,

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

另外从以下两点再区分以下这两种方式:

 

1、线程安全:

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。



2、资源加载和性能:

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,

而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

至于1、2、3这三种实现又有些区别,

第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,

第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

 

什么是线程安全?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。

 

应用

以下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式

    public class TestSingleton {  
        String name = null;  
      
            private TestSingleton() {  
        }  
      
        private static volatile TestSingleton instance = null;  
      
        public static TestSingleton getInstance() {  
               if (instance == null) {    
                 synchronized (TestSingleton.class) {    
                    if (instance == null) {    
                       instance = new TestSingleton();   
                    }    
                 }    
               }   
               return instance;  
        }  
      
        public String getName() {  
            return name;  
        }  
      
        public void setName(String name) {  
            this.name = name;  
        }  
      
        public void printInfo() {  
            System.out.println("the name is " + name);  
        }  
      
    }  
 

可以看到里面加了volatile关键字来声明单例对象,既然synchronized已经起到了多线程下原子性、有序性、可见性的作用,为什么还要加volatile呢,原因已经在下面评论中提到,

还有疑问可参考http://www.iteye.com/topic/652440
和http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

 

    public class TMain {  
        public static void main(String[] args){  
            TestStream ts1 = TestSingleton.getInstance();  
            ts1.setName("jason");  
            TestStream ts2 = TestSingleton.getInstance();  
            ts2.setName("0539");  
              
            ts1.printInfo();  
            ts2.printInfo();  
              
            if(ts1 == ts2){  
                System.out.println("创建的是同一个实例");  
            }else{  
                System.out.println("创建的不是同一个实例");  
            }  
        }  
    }  

 

 运行结果:

结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。

对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差别。

分享到:
评论

相关推荐

    Bootstrap 模板.md

    一些常用的 Bootstrap 模板示例,你可以根据自己的需求选择合适的模板,并进行定制以满足项目需求。Bootstrap 提供了丰富的组件和样式,可以帮助你快速搭建漂亮的网站和 Web 应用程序。 markdown文本,请使用vscode等代码编辑器查看!!!

    工地试验室人员统计表.docx

    工地试验室人员统计表.docx

    安卓音乐播放器应用及其源代码+使用说明(毕设参考)

    安卓音乐播放器应用及其源代码 概述 安卓音乐播放器应用是一款全能型音乐播放器,允许你在安卓设备上听自己的所有歌曲,并且可以免费流播。需要明确的是,这些免费歌曲绝不是非法的。它们是你可以在任何地方免费聆听的歌曲。 安卓音乐播放器让用户可以从自己的音乐库中选择想要播放的歌曲,然后在手机上播放。当你离开用户界面时,音乐不会停止。在你能做到这一点之前,你的电脑上需要安装一些东西。这样当你启动应用时,它会从你的设备中选择歌曲并播放。 音乐播放器让你可以快速轻松地管理和移动所有音乐文件。这个播放器可以播放大多数类型的mp3、midi、wav、flac raw和aac文件。它还可以播放其他类型的音频文件。音乐可以按照类型、专辑、艺术家、歌曲和文件夹进行分类,以便你可以快速找到想要的内容。 安卓音乐播放器:项目详情与技术 项目标题:安卓音乐播放器源代码 摘要:安卓音乐播放器应用让你以多种方式管理和播放你的数字音乐。 项目类型:移动应用 技术:Android Studio 数据库:SQLite 项目输出 安卓音乐播放器应用输出 如何运行安卓音乐播放器应用及其源代码

    《导师训练营》互联网项目的天花板,小白月入2w.txt

    《导师训练营》互联网项目的天花板,小白月入2w

    ASP基于WEB网上聊天室设计(源代码+论文)【ASP】.zip

    ASP基于WEB网上聊天室设计(源代码+论文)【ASP】

    ASP.net 销售管理系统项目源代码+使用说明

    ASP.net 中的销售管理系统项目是什么? ASP.net 中的销售管理系统项目,通常称为销售管理软件或销售 CRM,是一种旨在简化销售流程的软件应用程序。 销售人员可以利用该系统来维护联系、跟踪交易并通过委派管理工作来节省办公桌时间。 这是基本模型,但当今的销售管理系统要复杂得多,结合了广泛的数据,例如社交资料、在线活动、与网络中其他成员的关系以及其他特征。 当人工智能等新技术应用于这些数据时,销售人员可以获得适合其特定业务的预测和建议,从而建立更智能、切合主题的客户关系。 ASP.net 中销售管理系统的优点 通过减少管理活动和集中客户信息,销售管理系统可以提高团队效率。 该系统使销售经理能够将职责分配给适当的销售人员,优先考虑良好的销售线索,并改进预测和分析。 该系统使用 ASP.net的主要优点: 更精细的优先级划分——有效的销售 CRM 可帮助经理确定客户操作的优先级并将其分配给适当的销售代表。例如,根据对客户需求的分析,系统可以帮助员工定制针对客户的促销或捆绑服务。它还会建议和过滤潜在客户,以帮助您专注于最有可能为每种产品带来收入

    汽车起重机动力系统保养.doc

    汽车起重机动力系统保养.doc

    Java 员工管理系统项目源代码(可做毕设项目参考)

    Java 员工管理系统项目是一个基于 Java 编程语言开发的桌面应用程序,旨在管理员工的信息、津贴、扣除和薪资等功能。该系统通过提供结构和工具集,使公司能够有效地管理其员工数据和薪资流程。 系统特点 员工管理:管理员可以添加、查看和更新员工信息。 津贴管理:管理员可以添加和管理员工的津贴信息。 扣除管理:管理员可以添加和管理员工的扣除信息。 搜索功能:可以通过员工 ID 搜索员工详细信息。 更新薪资:管理员可以更新员工的薪资信息。 支付管理:处理员工的支付和生成支付记录。 模块介绍 员工管理模块:管理员可以添加、查看和更新员工信息,包括员工 ID、名字、姓氏、年龄、职位和薪资等。 津贴管理模块:管理员可以添加和管理员工的津贴信息,如医疗津贴、奖金和其他津贴。 扣除管理模块:管理员可以添加和管理员工的扣除信息,如税收和其他扣除。 搜索功能模块:可以通过员工 ID 搜索员工详细信息。 更新薪资模块:管理员可以更新员工的薪资信息。 支付管理模块:处理员工的支付和生成支付记录 可以作为毕业设计项目参考

    基于图像的机器人视觉伺服系统研究

    基于图像的机器人视觉伺服系统研究

    SLAM十四讲 Windows版本编译 的ceres、g2o以及使用方法

    slam 的ceres

    # 脉搏报警 1602(1).zip

    # 脉搏报警 1602(1).zip

    ASP某企业网络公寓管理系统的设计与实现(源代码+论文)【ASP】.zip

    ASP某企业网络公寓管理系统的设计与实现(源代码+论文)【ASP】

    bootstrap模板教程.docx

    bootstrap模板

    单机成本核算统计表.docx

    单机成本核算统计表.docx

    his_equal.v

    his_equal.v

    0基本系统的学习-短视频剪辑,剪辑软件-整套33节-无水印教程,全覆盖-视频剪辑作用.txt

    0基本系统的学习-短视频剪辑,剪辑软件-整套33节-无水印教程,全覆盖-视频剪辑作用

    360数字安全:2024年3月勒索软件流行态势分析报告

    勒索软件传播至今,360 反勒索服务已累计接收到数万勒索软件感染求助。随着新型勒索软件的快速蔓延,企业数据泄露风险不断上升,勒索金额在数百万到近亿美元的勒索案件不断出现。勒索软件给企业和个人带来的影响范围越来越广,危害性也越来越大。360全网安全大脑针对勒索软件进行了全方位的监测与防御,为需要帮助的用户提供 360 反勒索服务。 2024年3月,全球新增的活跃勒索软件家族有RAWorld、RedRansomware、Kill Security等,均为双重勒索病毒。 本月针对国内主流云服务器进行的勒索攻击比例大幅提高,从大量的云服务器用户反馈的案例看,相关系统均未安装 360终端安全产品进行勒索防护,被攻击的直接原因主要是 Web 服务漏洞、数据库弱口令登录、远程桌面弱口令登录。

    风险隐患排查治理清单.xls

    风险隐患排查治理清单.xls

    职工合理化建议技术革新项目申报表.doc

    职工合理化建议技术革新项目申报表.doc

    ASP基于BS的家教交流平台的实现(源代码+论文)【ASP】.zip

    ASP基于BS的家教交流平台的实现(源代码+论文)【ASP】

Global site tag (gtag.js) - Google Analytics