引言

在之前的文章中从技术以及源代码的层面上分析了关于Java高并发的解决方式。这篇博客主要介绍关于单例设计模式。关于单例设计模式大家应该不会陌生,作为GoF23中设计模式中最为基础的设计模式,实现起来比较简单但是能真正理解才是重要的一环。这里介绍七种关于单例设计模式的实现方式。

饿汉式

public class Singleton {private byte[] data = new byte[1024];//初始化实例private static Singleton instance = new Singleton();//私有构造方法private Singleton(){}//暴露实例的公告接口public static Singleton getInstance(){return instance;}
}

  从上面的代码中可以看到饿汉式的关键点就是在定义instance的时候就直接将其初始化,并且根据之前的博客我们知道如果类主动使用了Singleton类,那么这个类就是直接完成创建,包括其中的实例也会被创建。而instance作为类变量在类初始化的过程中也会收集到init方法中,这个方法能够百分之百实现同步,也就是说在多线程情况下不可能同一个实例被初始化两次,也就是说instance实例所开辟的堆内存会存在很长的时间。

  但是如果一个类中的内容比较多的时候,使用这种方式对于堆内存的占用还是比较大的,所以说这种方式只适合内容比较少的类区实现这样的方式。总的来说饿汉式就保证了实例的唯一性但无法实现懒加载,消耗的内存较多。

懒汉式

  从字面意思上理解懒汉式就是在实例真正使用的时候将其初始化,也就避免了在类初始化的时候就在堆内存中创建对应的实例。

z//懒汉式
public class Singleton1 {private byte[] bytes = new byte[1024];private static Singleton1 instance = null;private Singleton1(){}public static Singleton1 getInstance(){if (instance!=null){instance = new Singleton1();}return instance;}
}

  Singleton1 的类变量instance在定义的时候被初始化为null,也就是说在类初始化的时候并不会被实例化,在公共接口方法中看到对其进行实例化的判断,如果已经被实例化了则不会重复实例化,也就是说保证了实例化的唯一性,并不会保证单例的唯一性。

为什么不能保证单例的唯一性?
  现在有两个线程A线程和B线程,如果A线程在操作的时候发现instance为null,正在进行实例化操作的时候B线程过来也看到了instance为null。这就无法保证instance的唯一性。

懒汉同步方法式

  从上面的例子中可以看到,懒汉式只能保证实例的懒加载,并不能保证实例的唯一性,也就是说在多线程的情况下,instance被称为是共享资源,当多个线程共同访问的时候就难以保证数据的同步性,也就是说在懒汉式的时候要对其进行加工。

//懒加载+同步
public class Singleton2 {private byte[] data = new byte[1024];private static Singleton2 instance = new Singleton2();private Singleton2(){}public static synchronized  Singleton2 getInstance(){if (null!=instance){instance = new Singleton2();}return instance;}
}

  在使用了这种方式之后不但满足了懒加载而且保证了instance的唯一性,使用了synchronized关键字的排他性导致getInstance方法只能在同一时刻被同一个线程所访问,但是这样做的效率比较低。如果出现一个线程创建实例缓慢就会导致其他线程无法获取数据。

Double-Check(双重检测锁)

  对于Double-Check是一种比较好的方式来实现单例模式,但是也存在一些问题。

public class Singleton3 {private byte[] data = new byte[1024];private static Singleton3 instance = null;Connection conn;Socket socket;private Singleton3(){//        this.conn;
//        this.socket;}public static Singleton3 getInstance(){//当instance为null的时候,进入同步代码块,同时该判断也避免了每个线程都进入到同步代码块,可以提高效率if (null==instance){synchronized (Singleton3.class){//只有一个线程可以获取到Singleton3的锁if (null==instance){//如果instance为空则创建instance = new Singleton3();}}}return instance;}}

 &esmp;当两个线程发现null==instance成立的时候只有一个线程可以进入同步代码块,完成对instance的实例化,随后线程会发现instance为空则不需要进行任何的操作。正如前面提到的,这种方式看上去很巧妙,很完美,很高效。既满足了懒加载,又保证了唯一性。但是这种方式也有一定的风险,在多线程并发访问的时候回出现空指针异常。

为什么这种方式会出现空指针异常?
  在Singleton3的构造函数中分别实例化了两个资源,数据库连接和Socket,当然在初始化的时候还有Singleton3自身。根据JVM运行时指令重排和Happen-before原则,这三者之间的实例化顺序并没有前后约束的关系,也就是说在instance实例化完成之后Socket和Connection也没有被实例化,这样在调用的时候就会出现空指针,要知道这实例为单例的所以在第一次如果没有完全被初始化就不会进行第二次初始化操作。

Volatile+Double-Check

  从上面的分析中可以看到Double-Check的设计虽然比较巧妙,但是受到了JVM指令重排的影响,有可能会出现空指针而这个空指针没有办法在运行期间解决。那么怎么解决这个问题呢?要解决这个问题就要使用到前面提到的一个关键字volatile将代码修改为

public class Singleton3 {private byte[] data = new byte[1024];private  volatile static Singleton3 instance = null;Connection conn;Socket socket;private Singleton3(){//        this.conn;
//        this.socket;}public static Singleton3 getInstance(){//当instance为null的时候,进入同步代码块,同时该判断也避免了每个线程都进入到同步代码块,可以提高效率if (null==instance){synchronized (Singleton3.class){//只有一个线程可以获取到Singleton3的锁if (null==instance){//如果instance为空则创建instance = new Singleton3();}}}return instance;}}

  根据Volatile关键字的特点可以知道,将这个变量设置所有内存共享的变量。也就是说所有线程操作都获取到最新的状态,同时也防止了指令重排,那么Volatile是防止指令重排在之前的博客中也提到过。在编译的时候在编译器指令中插入一些屏障来保证指令顺序执行。

Holder的方式

  Holder完全是借助了类加载的特点。

public class Holder {private byte[] data = new byte[1024];private Holder(){}private static class Singleton{private static Singleton instance = new Singleton();}public static Singleton getInstatnce(){return Singleton.instance;}
}

  我们会看到在Holder类中并没有instance的静态成员,而是将其放到了静态内部类中,也就是说在实例化Holder的时候并不会创建Singleton的实例,Singleton定义了静态变量,并且直接可进行实例化,当Singleton被主动调用的时候就会创建Singleton的实例。这样既保证了唯一性也保证了懒加载。

枚举方式

  枚举方式作为在Java中比较少用的一种单例设计模式。所以在这里做简单的说明即可。

public enum  SingletonEnum {INSTANCE;private byte[] data = new byte[1024];SingletonEnum(){System.out.println("INSTANCE will be initialized immediately");}public static void method(){}public static SingletonEnum getInstance(){return INSTANCE;}
}

当然也可以对其进行修改,结合Holder方式

public class NewSingleton {private byte[] data = new byte[1024];private NewSingleton(){}private enum EnumHolder{INSTANCE;private NewSingleton instance;EnumHolder(){this.instance = new NewSingleton();}private  NewSingleton getInstance(){return instance;}}public static NewSingleton getInstance(){return EnumHolder.INSTANCE.getInstance();}
}

总结

  单例设计模式作为最简单的一种设计模式,能在多线程的场景下使用好单例模式是对自己能力的一个提升,本篇博客中简单的介绍了七种单例设计模式结合之前的博客以及各种资料做了简单的分析,从最简单的代码中解决最为高深的问题。

Java高并发编程详解系列-7种单例模式相关推荐

  1. 《JAVA高并发编程详解》-七种单例模式

    转载于:https://www.cnblogs.com/zhujiqian/p/10811317.html

  2. Java高并发编程详解系列-Java线程入门

    根据自己学的知识加上从各个网站上收集的资料分享一下关于java高并发编程的知识点.对于代码示例会以Maven工程的形式分享到个人的GitHub上面.   首先介绍一下这个系列的东西是什么,这个系列自己 ...

  3. Java高并发编程详解系列-线程上下文设计模式及ThreadLocal详解

    导语   在之前的分享中提到过一个概念就是线程之间的通信,都知道在线程之间的通信是一件很消耗资源的事情.但是又不得不去做的一件事情.为了保证多线程线程安全就必须进行线程之间的通信,保证每个线程获取到的 ...

  4. Java高并发编程详解系列-Future设计模式

    导语   假设,在一个使用场景中有一个任务需要执行比较长的时间,通常需要等待任务执行结束之后或者是中途出错之后才能返回结果.在这个期间调用者只能等待,对于这个结果Future设计模式提供了一种凭据式的 ...

  5. Java高并发编程详解系列-类加载

    之前在写关于JVM的时候提到过类加载机制,类加载机制也是在Java面试中被经常问道的一个问题,在这篇博客中就来了解一下关于类加载的知识. 类加载   在JVM执行Java程序的时候实际上执行的编译好的 ...

  6. Java高并发编程详解系列-线程安全数据同步

    在多线程中最为复杂和最为重要的就是线程安全.多个线程访问同一个对象的时候会导致线程安全问题.通过加锁可以避免这种问题.但是在串行执行的过程中又不用考虑线程安全问题,而使用串行程序效率低没有办法将CPU ...

  7. Java高并发编程详解系列-不可变对象设计模式

    导语   在开发中涉及到的所有关于多线程的问题都离不开共享资源的存在.那么什么是共享资源,共享资源就是被多个线程共同访问的数据资源,而且每个线程都会引起它的变化.伴随共享资源而生的新问题就是线程安全, ...

  8. Java高并发编程详解系列-线程上下文类加载

    前面的分享中提到的最多的概念就是关于类加载器的概念,但是当我们查看Thread源码的时候会发现如下的两个方法,这两个方法就是获取或者设置线程的上下文类加载器的方法,那么为什么要设置这两个方法呢?这个就 ...

  9. Java高并发编程详解系列-线程通信

      进程间的通信,又被称为是进程内部的通信,我们都知道每个进程中有多个线程在执行,多个线程要互斥的访问共享资源的时候会发送对应的等待信号或者是唤醒线程执行等信号.那么这些信号背后还有什么样的技术支持呢 ...

最新文章

  1. VMware虚拟机上安装Linux系统
  2. C#线程同步(1)- 临界区&Lock .
  3. jslint4java_JSLint检测javascript的错误提示
  4. windows获取系统补丁+匿名管道
  5. 关联性挖掘--Apriori算法详解
  6. php bloginfo templatedirectory,PHP变量不显示使用bloginfo('template_directory')的图像
  7. 房贷提前还款怎么还?
  8. Android平台下的ToDoList
  9. 微型计算机主要特点,微型计算机数字控制的主要特点.ppt
  10. aop日志 android,ASM实现Android APK的AOP日志统计
  11. aforge 学习-命名空间中文理解
  12. windows下Navicat 过期如何解决
  13. 问卷设计一:问卷题目哪些有类型和注意要点?
  14. 在企业工作一年多的几点感悟
  15. docker mysql 修改cnf_Docker下Mysql .cnf文件修改小贴士
  16. win10 全局快捷键设置启动程序
  17. 日常英语精彩短句集锦
  18. web端接收读卡器卡片信息
  19. ppt教学课件制作的原则、规律和授课技巧
  20. OLAP实践 —— OLAP基本概念理解总计小记

热门文章

  1. linux 堆的作用,闭包、对象,以及堆“族” | Linux 中国
  2. php怎样使用pdo,PHP中使用PDO_PHP教程
  3. [MySQL] mysql 的行级显式锁定和悲观锁
  4. centos7 改虚拟网卡名称
  5. dom4j解析xml的简单实用
  6. 面向对象开发方法概述
  7. 从页面底部向上弹出dialog,消失时逐渐向下(转)
  8. rsync和inotify实时同步配置 exclude排除多个文件夹
  9. 解决svn cannot set LC_CTYPE locale的问题
  10. 人魔比妖都恶的时代...