单例顾名思义就是一个实例。类只有唯一一个实例,并提供给全局使用。解决了全局使用的类频繁地创建与销毁带了的消耗。

单例模式常用简单,但细究却又不简单,且往下看。

单例模式又可以分为

(1)懒汉式:需要使用实例时,才创建实例

(2)饿汉式:类加载时,就创建静态实例。

上代码

1、饿汉式,线程安全

  1. /**
  2. * 饿汉式--线程安全
  3. * 优点:没有加锁,执行效率会提高。
  4. * 缺点:类加载时就初始化,浪费内存。
  5. */
  6. public class UserHunger_Safe {
  7. /**
  8. * 对象实例
  9. */
  10. private static UserHunger_Safe instance = new UserHunger_Safe();
  11.  
  12. /**
  13. * 私有构造函数
  14. */
  15. private UserHunger_Safe() {
  16. }
  17.  
  18. /**
  19. * 对外提供公共获取实例方法(线程安全)
  20. *
  21. * @return
  22. */
  23. public static UserHunger_Safe getInstance() {
  24. return instance;
  25. }
  26. }

UserHunger_Safe.java

2、懒汉式,线程不安全

  1. /**
  2. * 懒汉式单例模式
  3. * 线程不安全
  4. */
  5. public class UserLazy_Unsafe {
  6.  
  7. /**
  8. * 私有构造方法
  9. */
  10. private UserLazy_Unsafe() {
  11. }
  12.  
  13. /**
  14. * 对象实例
  15. */
  16. private static UserLazy_Unsafe instance;
  17.  
  18. /**
  19. * 对外提供公共获取实例方法(线程不安全)
  20. *
  21. * @return
  22. */
  23. public static UserLazy_Unsafe getInstance() {
  24. if (instance == null) {
  25. instance = new UserLazy_Unsafe();
  26. }
  27. return instance;
  28. }
  29. }

UserLazy_Unsafe.java

3、懒汉式--方法加锁synchronized,线程安全

  1. /**
  2. * 懒汉式单例模式
  3. * 优点:第一次调用才初始化,避免内存浪费。
  4. * 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
  5. */
  6. public class UserLazy_Safe {
  7. /**
  8. * 私有构造方法
  9. */
  10. private UserLazy_Safe() {
  11. }
  12.  
  13. /**
  14. * 对象实例
  15. */
  16. private static UserLazy_Safe instance;
  17.  
  18. /**
  19. * 对外提供公共获取实例方法(线程安全)
  20. *
  21. * @return
  22. */
  23. public static synchronized UserLazy_Safe getInstance() {
  24. if (instance == null) {
  25. instance = new UserLazy_Safe();
  26. }
  27. return instance;
  28. }
  29. }

UserLazy_Safe.java

方法加锁,则每次调用都需要锁定,严重影响性能,这不是想要的。所以优化下得出第4中方案

4、双重校验锁(DCL,即 double-checked locking)

  1. /**
  2. * 饿汉式+双重校验锁
  3. */
  4. public class UserDCL_Safe {
  5. /**
  6. * 对象实例
  7. */
  8. private static UserDCL_Safe instance;
  9.  
  10. /**
  11. * 私有构造函数
  12. */
  13. private UserDCL_Safe() {
  14. }
  15.  
  16. /**
  17. * 对外提供公共获取实例方法(线程安全)
  18. *
  19. * @return
  20. */
  21. public static synchronized UserDCL_Safe getInstance() {
  22. if (instance == null) {
  23. //双重校验锁
  24. synchronized (UserDCL_Safe.class) {
  25. if (instance == null) {
  26. instance = new UserDCL_Safe();
  27. }
  28. }
  29. }
  30. return instance;
  31. }
  32. }

UserDCL_Safe.java

如此一来,即便是多线程也能保证安全。如果只考虑静态域还可以使用内部静态类

5、静态内部类

  1. /**
  2. * 登记式/静态内部类
  3. * 线程安全
  4. * 这种方式能达到双检锁方式一样的功效,但实现更简单。
  5. * 这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
  6. */
  7. public class UserStaticInnerClass_Safe {
  8. private static class SingletonHolder{
  9. private static final UserStaticInnerClass_Safe INSTANCE=new UserStaticInnerClass_Safe();//实例
  10. }
  11.  
  12. //私有构造函数
  13. private UserStaticInnerClass_Safe(){}
  14. public static final UserStaticInnerClass_Safe getInstance(){
  15. return SingletonHolder.INSTANCE;
  16. }
  17. }

UserStaticInnerClass_Safe.java

至此,单例模式是不是绝对安全了?答案当然不是,在反射攻击下,外部依旧能做到非单例,不信,读者可以通过反射方式获得以上1,3,4,5例子的实例,就会发现依旧可以得到不同的实例。那有什么好的解决方案呢?

这个Effective Java 作者 Josh Bloch 大神给出了解决方案--使用枚举

6、枚举,最佳实现方式

  1. /**
  2. * 枚举方式实现单例模式
  3. */
  4. public enum UserEnum {
  5. INSTANCE;
  6.  
  7. /**
  8. * 姓名
  9. */
  10. private String name;
  11.  
  12. /**
  13. * 年龄
  14. */
  15. private int age;
  16.  
  17. /**
  18. * 手机号码
  19. */
  20. private String telephone;
  21.  
  22. public String getName() {
  23. return name;
  24. }
  25.  
  26. public void setName(String name) {
  27. this.name = name;
  28. }
  29.  
  30. public int getAge() {
  31. return age;
  32. }
  33.  
  34. public void setAge(int age) {
  35. this.age = age;
  36. }
  37.  
  38. public String getTelephone() {
  39. return telephone;
  40. }
  41.  
  42. public void setTelephone(String telephone) {
  43. this.telephone = telephone;
  44. }
  45.  
  46. @Override
  47. public String toString() {
  48. return "SingletonPattern_EnumType{" +
  49. "name='" + name + '\'' +
  50. ", age=" + age +
  51. ", telephone='" + telephone + '\'' +
  52. '}';
  53. }
  54. }

UserEnum.java

如此一来既保证了线程安全,也保证了反序列化安全

测试

  1. import java.lang.reflect.Constructor;
  2.  
  3. public class SigletonPattern {
  4. public static void main(String[] args) throws Exception {
  5. //懒汉式单例--线程不安全
  6. UserLazy_Unsafe ul1 = UserLazy_Unsafe.getInstance();
  7. UserLazy_Unsafe ul2 = UserLazy_Unsafe.getInstance();
  8. System.out.println(ul1 + "\n" + ul2);
  9. System.out.println("===========懒汉式单例--线程不安全 End==========");
  10.  
  11. //懒汉式单例--线程安全.获取实例方法添加synchronized,加锁会影响效率。
  12. UserLazy_Safe uls1 = UserLazy_Safe.getInstance();
  13. UserLazy_Safe uls2 = UserLazy_Safe.getInstance();
  14. System.out.println(uls1 + "\n" + uls2);
  15. System.out.println("===========懒汉式单例--线程安全 End==========");
  16.  
  17. //懒汉式单例--登记式/静态内部类--线程安全。
  18. UserStaticInnerClass_Safe usic = UserStaticInnerClass_Safe.getInstance();
  19. UserStaticInnerClass_Safe usic2 = UserStaticInnerClass_Safe.getInstance();
  20. System.out.println(usic + "\n" + usic2);
  21. System.out.println("===========懒汉式单例--登记式/静态内部类--线程安全 End==========");
  22.  
  23. UserEnum user = UserEnum.INSTANCE;
  24. //UserEnum user = new UserEnum();
  25. user.setName("科技无国界");
  26. user.setAge(0);
  27. user.setTelephone("16895965423");
  28.  
  29. UserEnum levon = UserEnum.INSTANCE;
  30. //UserEnum levon = new UserEnum();
  31. levon.setName("联想--科技无国界");
  32. levon.setAge(10);
  33. levon.setTelephone("26895965423");
  34.  
  35. System.out.println(user);
  36. System.out.println(levon);
  37.  
  38. //通过反射获得对象
  39. Constructor<UserEnum> constructor = UserEnum.class.getDeclaredConstructor();
  40. constructor.setAccessible(true);
  41. UserEnum sp = constructor.newInstance();
  42. System.out.println(sp);
  43. }
  44. }

main方法

  1. UserLazy_Unsafe@1b6d3586
  2. UserLazy_Unsafe@1b6d3586
  3. ===========懒汉式单例--线程不安全 End==========
  4. UserLazy_Safe@4554617c
  5. UserLazy_Safe@4554617c
  6. ===========懒汉式单例--线程安全 End==========
  7. UserStaticInnerClass_Safe@74a14482
  8. UserStaticInnerClass_Safe@74a14482
  9. ===========懒汉式单例--登记式/静态内部类--线程安全 End==========
  10. SingletonPattern_EnumType{name='联想--科技无国界', age=10, telephone='26895965423'}
  11. SingletonPattern_EnumType{name='联想--科技无国界', age=10, telephone='26895965423'}
  12. Exception in thread "main" java.lang.NoSuchMethodException: UserEnum.<init>()
  13. at java.lang.Class.getConstructor0(Class.java:3082)
  14. at java.lang.Class.getDeclaredConstructor(Class.java:2178)
  15. at SigletonPattern.main(SigletonPattern.java:39)

输出结果

示例源码:https://github.com/LF20160912/pattern

设计模式Design Pattern(2)--单例模式的更多相关文章

  1. 【Java】【设计模式 Design Pattern】单例模式 Singleton

    什么是设计模式? 设计模式是在大量的实践中总结和理论化之后的最佳的类设计结构,编程风格,和解决问题的方式 设计模式已经帮助我们想好了所有可能的设计问题,总结在这些各种各样的设计模式当中,也成为GOF2 ...

  2. 设计模式(Design Pattern)系列之.NET专题

    最近,不是特别忙,重新翻了下设计模式,特地在此记录一下.会不定期更新本系列专题文章. 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结. 使用 ...

  3. 设计模式Design Pattern(1)--简介

    什么是设计模式? 软件开发人员在长期实践中总结出来的解决特定问题的一套解决方案. 对象设计原则 计模式主要是基于以下的面向对象设计原则. 对接口编程而不是对实现编程. 优先使用对象组合而不是继承. 设 ...

  4. [Java复习] 设计模式 Design Pattern

    设计模式的六大原则 1.开闭原则(Open Close Principle) 对扩展开放,对修改关闭. 2.里氏代换原则(Liskov Substitution Principle) 任何基类可以出现 ...

  5. 设计模式のSingleton Pattern(单例模式)----创建模式

    单例模式没有什么好讲的,我们 举个例子 #region 单例定义 /// <summary> /// 类单例 /// </summary> private static Win ...

  6. 设计模式Design Pattern(4) -- 访问者模式

    什么是访问者模式? 一个对象有稳定的数据结构,却为不同的访问者提供不同的数据操作,对象提供接收访问者的方法,从而保证数据结构的稳定性和操作的多样性.也可以理解为,封装对象的操作方法,达到不改变对象数据 ...

  7. 设计模式Design Pattern(3) -- 责任链模式

    什么是责任链模式? 责任链模式(Chain of Responsibility Pattern):请求知道公开接口,但不知道那个具体类处理,这些具体处理类对象连接成一条链.请求沿着这条链传递,直到有对 ...

  8. Design Principle vs Design Pattern 设计原则 vs 设计模式

    Design Principle vs Design Pattern设计原则 vs 设计模式 来源:https://www.tutorialsteacher.com/articles/differen ...

  9. 简单工厂设计模式(Simple Factory Design Pattern)

    [引言]最近在Youtub上面看到一个讲解.net设计模式的视频,其中作者的一个理解让我印象很深刻:所谓的设计模式其实就是运用面向对象编程的思想来解决平时代码中的紧耦合,低扩展的问题.另外一点比较有见 ...

随机推荐

  1. 【linux】杀掉进程命令

    1.找到对应的进程 通过端口查找 lsof -i:端口号 netstat -tunlp | grep 端口   lsof -i:9500   netstat -tunlp | grep 9500 2. ...

  2. day68—angularJS学习笔记之-过滤器

    转行学开发,代码100天——2018-05-23 今天学习angularJS的过滤器的使用. angular中的常用过滤器用来修改数据格式,主要有以下几类: 1.大写,| uppercase 2.小写 ...

  3. day67—angularJS学习笔记控制器

    转行学开发,代码100天——2018-05-22 angularJS通过控制器来控制数据流的应用. ng-controller. 控制器中包含属性和函数,其参数引用通过 $scope来执行. 如下文的 ...

  4. Java ——继承

    本节重点思维导图 面向对象三大特性:封装.继承.多态 面向对象编程的第一原则:面向接口编程 继承 代码利用的方式,子类与父类之间的关系 语法 访问控制修饰符 class 类名 extends父类名{ ...

  5. 打印一个浮点数组,会输出字符串"Hello, world“ & 浮点数的二进制表示(IEEE 754标准)

    #include <stdio.h> #include<stdlib.h> int main() { float a[3] = { 1143139122437582505939 ...

  6. lnmp一键安装包卸载mysql,重新安装报错mysql57-community-release conflicts with mysql-community-release-el6-5.noarch

    环境:CentOS Linux release 7.6.1810 lnmp1.5 独立下载mysql仓库 wget -i -c http://dev.mysql.com/get/mysql57-com ...

  7. pandas DataFram的insert函数

    原文链接:https://blog.csdn.net/yanwucao/article/details/80211984 DataFrame.insert(loc, column, value, al ...

  8. 应用安全 - CMS - Discuz漏洞汇总

    SSV-90861 Date:2012 类型:敏感信息泄露 影响范围:DZ x2.5  POC:http://www.xx.xx/uc_server/control/admin/db.php http ...

  9. Miller-Robin 素数测试法 模板

    测试单个素数,出错概率比计算机本身出错的概率还要低 算法是基于费马小定理(format),二次探测定理(x*x % p == 1 ,若P为素数,则x的解只能是x = 1或者x = p - 1)加上迭代 ...

  10. MySQL服务意外停止

    先说一下,发现MySQL服务停了,启动就又好了,但是好奇服务意外停止的原因,所以看了一下MySQL的错误日志. 但是到底是哪个错误导致MySQL服务意外终止,还没有定论,故有了此篇文章,还望知道原因的 ...