第六章:单利模式,懒汉式,饿汉式以及静态内部类,双重检查

第六章:单利模式,懒汉式,饿汉式以及静态内部类,双重检查第六章:单利模式,懒汉式,饿汉式以及静态内部类,双重检查

大家好,又见面了,我是你们的朋友全栈君。


文章转自:https://blog.csdn.net/u012453843/article/details/73743997

单例模式,最常见的就是饥饿模式和懒汉模式,一个直接实例化对象,一个在调用方法时进行实例化对象。

       大家见的最多的莫过于下面这种单例模式了,这种模式是懒汉模式,就是说只有你调用getInstance方法的时候,它才会创建实例。但是这种方式有个非常致命的问题就是在多线程的情况下不能正常工作。

[html] 
view plain
 copy

  1. public class Singleton {    
  2.     private static Singleton instance;    
  3.     private Singleton (){}    
  4.     
  5.     public static Singleton getInstance() {    
  6.        if (instance == null) {    
  7.           instance = new Singleton();    
  8.        }    
  9.        return instance;    
  10.     }    
  11. }   

        懒汉模式要想线程安全,大家第一想到的便是下面这种方式,就是在getInstance方法加上synchronized关键字,但是这种方式也有致命的缺点,那就是并发率太低。

[html] 
view plain
 copy

  1. public class Singleton {    
  2.     private static Singleton instance;    
  3.     private Singleton (){}    
  4.     public static synchronized Singleton getInstance() {    
  5.        if (instance == null) {    
  6.           instance = new Singleton();    
  7.        }    
  8.        return instance;    
  9.     }    
  10. }  

        上面是懒汉模式,下面我们再看下饿汉模式,如下所示。饿汉模式是典型的空间换取时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候就不需要再判断了,节省了运行时间。但如果一直没有人调用,这种浪费的空间就不值得,特别是在空间不足的情况下。

[html] 
view plain
 copy

  1. public class Singleton {    
  2.     private static Singleton instance = new Singleton();    
  3.     private Singleton (){}    
  4.     public static Singleton getInstance() {    
  5.        return instance;    
  6.     }    
  7. }   

         在多线程模式中,考虑到性能和线程安全问题,我们一般选择下面两种比较经典的单例模式,在性能提高的同时,又保证了线程安全。


第一种方式:静态内部类


         这种方式是最好的单例模式,而且还是线程安全的,为何这么说呢,静态内部类Singleton在初始化过程中是不会被加载的,只有当用户调用共用的getInstance方法时才会加载内部类Singleton并且实例化Singleton实例,也就是说,静态内部类这种方式也属于懒汉模式,只是实现方式不一样而已。之所以是线程安全的,是因为Singleton是静态的,静态内部类只会被实例化一次,也就是说不管有多少线程,大家拿到的是同一个实例,不会再去进行多次实例化,从而达到了线程安全的目的。由于没有加锁,所以并发性特别高,线程还安全,所以大家以后碰到单例模式,用静态内部类最为合适。如下图所示。

          代码如下:

[html] 
view plain
 copy

  1. package com.internet.singleton;  
  2.   
  3. public class InnerSingleton {  
  4.     private static class Singleton{  
  5.         private static InnerSingleton single = new InnerSingleton();  
  6.     }  
  7.       
  8.     public static InnerSingleton getInstance(){  
  9.         return Singleton.single;  
  10.     }  
  11. }  

第二种方式:双重检查


           首先,我说一下这种设计的初衷是什么,我们看到了getDs()方法中添加了类锁(synchronized (DubbleSingleton.class)),这种锁直接把整个类都锁住了,其它线程访问这个类的任何方法都要排队等候,设计者目的是当第一个线程访问getDs()方法时,它把整个类锁住,然后它把这个类实例化,然后后面的线程进入getDs()方法后一判断发现ds已经不是null了,于是便把第一个线程实例化好的实例返回。这样后续的线程无论并发有多少都没有问题,也不用再排队,直接拿取实例化好的实例即可。既然这样,为何在synchronized (DubbleSingleton.class)当中再判断一次ds是否为null呢?这个其实也好理解,synchronized上面不是模拟了一下初始化对象的准备时间吗?这个模拟是很有必要的,当第一个线程进入getDs()方法后,判断了一下df是否为null,发现是null,然后它就进入初始化对象的准备工作中去了,这个过程可能需要几秒钟,注意这时第一个线程还没有执行到synchronized这一行代码,也就是说还没有加上类锁,在这个过程当中假如又有多个线程要调用getDs()方法,它们便可以同时访问这个方法,这些线程判断ds依然是null,因此会进入到if判断里面,这些线程也进入到初始化对象的准备过程,等到第一个线程初始化对象准备完毕之后,它进入到synchronized这块代码处,给整个类加上了锁,这时后续再有线程的话,就要排队等候调用getDs()了。等第一个线程执行完实例化代码之后,刚才那些趁第一个线程没有锁住类的间隙偷偷摸进来的线程便也都进入到synchronized代码处,这些线程会依次锁住类进入到synchronized代码块中,这时如果代码块中不加一层if判断的话,就会再实例化一次DubbleSingleton类并返回,有几个这样偷溜进来的线程便会实例化几次Dubblesingleton实例。除了这些偷溜进来的线程之外,再来访问的线程由于在类外等待第一个线程执行完之后才有机会进入到方法体中,这时ds早已经实例化过了,因此第一个if判断便不成立,于是直接把当前Dubblesingleton实例返回。也就是说,后面的线程便可以高并发了,不用受锁的限制了。而如果在synchronized锁内再加一层判断的话,由于第二个线程进入锁内时,第一个线程肯定已经执行完了(synchronized这时扮演的是类锁),因此这时ds肯定不是null了,第二个线程一判断,发现ds不为null了,便直接把第一个线程实例化好的实例返回了,同理,其它线程也把第一个线程实例化好的实例返回。从而保证了线程安全。


         双重检查与懒汉加锁模式最大的区别在于,双重检查加锁只是一瞬间的事儿,后续无论有多少个线程都可以自由访问,没有线程安全问题。而懒汉加锁模式由于每个线程都要排队访问getInstance方法,因此效率太低。这就是他们之间的区别。双重检查的代码类如下图所示。


第六章:单利模式,懒汉式,饿汉式以及静态内部类,双重检查

          代码如下:

[html] 
view plain
 copy

  1. package com.internet.singleton;  
  2.   
  3. public class DubbleSingleton {  
  4.       
  5.     private static DubbleSingleton ds;  
  6.       
  7.     public static DubbleSingleton getDs(){  
  8.         if(ds == null){  
  9.             try {  
  10.                 //模拟初始化对象的准备时间…  
  11.                 Thread.sleep(3000);  
  12.             } catch (Exception e) {  
  13.                 e.printStackTrace();  
  14.             }  
  15.             synchronized (DubbleSingleton.class) {  
  16.                 if(ds == null){  
  17.                     ds = new DubbleSingleton();  
  18.                 }  
  19.             }  
  20.         }  
  21.         return ds;  
  22.     }  
  23.       
  24.     public static void main(String[] args){  
  25.         Thread t1 = new Thread(new Runnable() {  
  26.               
  27.             @Override  
  28.             public void run() {  
  29.                 System.out.println(DubbleSingleton.getDs().hashCode());  
  30.             }  
  31.         },”t1″);  
  32.           
  33.         Thread t2 = new Thread(new Runnable() {  
  34.               
  35.             @Override  
  36.             public void run() {  
  37.                 System.out.println(DubbleSingleton.getDs().hashCode());  
  38.             }  
  39.         },”t2″);  
  40.           
  41.                 Thread t3 = new Thread(new Runnable() {  
  42.               
  43.             @Override  
  44.             public void run() {  
  45.                 System.out.println(DubbleSingleton.getDs().hashCode());  
  46.             }  
  47.         },”t3″);  
  48.           
  49.                 t1.start();  
  50.                 t2.start();  
  51.                 t3.start();  
  52.     }  
  53. }  

         运行上面双重检查代码,结果如下图所示,可见,实例是一样的。

第六章:单利模式,懒汉式,饿汉式以及静态内部类,双重检查

         如果把synchronized代码块中的if判断去掉,如下图所示。

第六章:单利模式,懒汉式,饿汉式以及静态内部类,双重检查

          再执行main方法,结果如下图所示,发现三次结果都不一样,说明有线程安全问题。

第六章:单利模式,懒汉式,饿汉式以及静态内部类,双重检查
           综合以上所有情况,可以知道,用静态内部类是最好的单例模式。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/106146.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • php网页如何发布_php问题

    php网页如何发布_php问题如何正确发布PHP代码几乎每一个PHP程序员都发布过代码,可能是通过FTP或者rsync同步的,也可能是通过svn或者git更新的。一个活跃的项目可能每天都要发布若干次代码,但是现实却是很少有人注意其中的细节,实际上这里面有好多坑,很可能你就在坑中却浑然不知。一个正确实现的发布系统至少应该支持原子发布。如果说每一个版本都表示一个独立的状态的话,那么在发布期间,任何…

    2022年9月27日
    1
  • mysql数据库存储过程讲解与实例分析_数据库存储过程的优点

    mysql数据库存储过程讲解与实例分析_数据库存储过程的优点存储过程简介SQL语句需要先编译然后执行,而存储过程(StoredProcedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。存储过程是可编程的函数,在数据库中创建并保存,可以由SQL语句和控制结构组成。当想要在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过…

    2025年7月31日
    2
  • tcping在linux用法,tcping的安装和使用[通俗易懂]

    tcping在linux用法,tcping的安装和使用[通俗易懂]Tcping网上比较少资料是关于linux对tcp端口ping测试的方法,我这里简单介绍2种方法:1.yum安装,编译安装方法1-1.wgethttp://linuxco.de/tcping/tcping-1.3.5.tar.gz###下载tcping1-2tarzxvftcping-1.3.5.tar.gz####解压缩tcping-1.3.51-3cdtcping-1….

    2022年6月23日
    105
  • html中的空格怎么写,html中空格代码是什么?(总结)

    html中的空格怎么写,html中空格代码是什么?(总结)首页>web前端>html教程>正文HTML中空格代码是什么?(总结)原创2018-08-对于新手小白来说,html空格符号代码的书写可能有点茫然,那么本篇文章就给大家总结介绍关于html空格代码的多种表示方法。希望对大家有一定的帮助。HTML提供了6种空格(),它们拥有不同的宽度。一、nbsp;非断行空格,是常规空格的宽度,可运行于所有主流浏览器。其它几种空格(ensp;、em…

    2022年6月21日
    70
  • explain如何查看mysql_MySQL Explain详解[通俗易懂]

    explain如何查看mysql_MySQL Explain详解[通俗易懂]在日常工作中,我们会有时会开慢查询去记录一些执行时间比较久的SQL语句,找出这些SQL语句并不意味着完事了,些时我们常常用到explain这个命令来查看一个这些SQL语句的执行计划,查看该SQL语句有没有使用上了索引,有没有做全表扫描,这都可以通过explain命令来查看。所以我们深入了解MySQL的基于开销的优化器,还可以获得很多可能被优化器考虑到的访问策略的细节,以及当运行SQL语句时哪种策略…

    2022年10月18日
    2
  • C#使用ManagementObjectSearcher获取本计算机CPU,硬盘,内存条等相关设备信息

    C#使用ManagementObjectSearcher获取本计算机CPU,硬盘,内存条等相关设备信息C#获取本操作系统显卡,CPU,硬盘等信息

    2022年10月2日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号