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

这里主要详细介绍两种:懒汉式和饿汉式

一、立即加载/饿汉式

在调用方法前,实例就已经被创建,代码:

  1. package com.weishiyao.learn.day8.singleton.ep1;
  2.  
  3. public class MyObject {
  4. // 立即加载方式==恶汉模式
  5. private static MyObject myObject = new MyObject();
  6.  
  7. private MyObject() {
  8. }
  9.  
  10. public static MyObject getInstance() {
  11. // 此代码版本为立即加载
  12. // 此版本代码的缺点是不能有其他实例变量
  13. // 因为getInstance()方法没有同步
  14. // 所以有可能出现非线程安全的问题
  15. return myObject;
  16. }
  17. }

创建线程类

  1. package com.weishiyao.learn.day8.singleton.ep1;
  2.  
  3. public class MyThread extends Thread {
  4. @Override
  5. public void run() {
  6. System.out.println(MyObject.getInstance().hashCode());
  7. }
  8. }

创建运行类

  1. package com.weishiyao.learn.day8.singleton.ep1;
  2.  
  3. public class Run {
  4. public static void main(String[] args) {
  5. MyThread t1 = new MyThread();
  6. MyThread t2 = new MyThread();
  7. MyThread t3 = new MyThread();
  8. t1.start();
  9. t2.start();
  10. t3.start();
  11. }
  12. }

运行结果

  1. 167772895
  2. 167772895
  3. 167772895

hashCode是同一个值,说明对象也是同一个,说明实现了立即加载型的单利模式

二、延迟加载/懒汉式

在调用方法以后实例才会被创建,实现方案可以是将实例化放到无参构造函数当中,这样只有当调用的时候才会创建对象的实例,代码:

  1. package com.weishiyao.learn.day8.singleton.ep2;
  2.  
  3. public class MyObject {
  4. private static MyObject myObject;
  5.  
  6. private MyObject() {
  7.  
  8. }
  9.  
  10. public static MyObject getInstance() {
  11. // 延迟加载
  12. if (myObject != null) {
  13.  
  14. } else {
  15. myObject = new MyObject();
  16. }
  17. return myObject;
  18. }
  19. }

创建线程类

  1. package com.weishiyao.learn.day8.singleton.ep2;
  2.  
  3. public class MyThread extends Thread {
  4. @Override
  5. public void run() {
  6. System.out.println(MyObject.getInstance().hashCode());
  7. }
  8. }

创建运行类

  1. package com.weishiyao.learn.day8.singleton.ep2;
  2.  
  3. public class Run {
  4. public static void main(String[] args) {
  5. MyThread t1 = new MyThread();
  6. t1.start();
  7. }
  8. }

运行结果

  1. 167772895

这样虽然取出了一个对象的实例,但是如果在多线程的环境中,就会出现多个实例的情况,这样就不是单例模式了

运行测试类

  1. package com.weishiyao.learn.day8.singleton.ep2;
  2.  
  3. public class Run {
  4. public static void main(String[] args) {
  5. MyThread t1 = new MyThread();
  6. MyThread t2 = new MyThread();
  7. MyThread t3 = new MyThread();
  8. MyThread t4 = new MyThread();
  9. MyThread t5 = new MyThread();
  10. t1.start();
  11. t2.start();
  12. t3.start();
  13. t4.start();
  14. t5.start();
  15. }
  16. }

运行结果

  1. 980258163
  2. 1224717057
  3. 1851889404
  4. 188820504
  5. 1672864109

既然出现问题,就要解决问题,在懒汉模式中的多线程的解决方案,代码:

第一种方案,最常见的,加synchronized,而synchronized可以加到不同的位置

第一种,方法锁

  1. package com.weishiyao.learn.day8.singleton.ep3;
  2.  
  3. public class MyObject {
  4. private static MyObject myObject;
  5.  
  6. private MyObject() {
  7.  
  8. }
  9.  
  10. synchronized public static MyObject getInstance() {
  11. // 延迟加载
  12. try {
  13. if (myObject != null) {
  14.  
  15. } else {
  16. // 模拟在创建对象之前做一些准备性的工作
  17. Thread.sleep();
    myObject = new MyObject();
    }
  18.  
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. return myObject;
  23. }
  24. }

这种synchronized的同步方案导致效率过于低下,整个方法都被锁住

第二种synchronized使用方案

  1. package com.weishiyao.learn.day8.singleton.ep3;
  2.  
  3. public class MyObject {
  4. private static MyObject myObject;
  5.  
  6. private MyObject() {
  7.  
  8. }
  9.  
  10. public static MyObject getInstance() {
  11. // 延迟加载
  12. try {
  13. synchronized (MyObject.class) {
  14. if (myObject != null) {
  15.  
  16. } else {
  17. // 模拟在创建对象之前做一些准备性的工作
  18. Thread.sleep();
    myObject = new MyObject();
    }
  19. }
  20.  
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. return myObject;
  25. }
  26. }

这种方法效率一样很低,方法内的所有代码都被锁住,只需要锁住关键代码就好,第三种synchronized使用方案

  1. package com.weishiyao.learn.day8.singleton.ep3;
  2.  
  3. public class MyObject {
  4. private static MyObject myObject;
  5.  
  6. private MyObject() {
  7.  
  8. }
  9.  
  10. public static MyObject getInstance() {
  11. // 延迟加载
  12. try {
  13. if (myObject != null) {
  14.  
  15. } else {
  16. // 模拟在创建对象之前做一些准备性的工作
  17. Thread.sleep();
  18. synchronized (MyObject.class) {
  19. myObject = new MyObject();
  20. }
  21. }
  22.  
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. return myObject;
  27. }
  28. }

这么写看似是最优方案了,但是,运行一下结果,发现,其实它是非线程安全的

结果:

  1.  

Why?

虽然锁住了对象创建的语句,每次只能有一个线程完成创建,但是,当第一个线程进来创建完成Object对象以后,第二个线程进来还是可以继续创建的,因为我们紧紧只锁住了创建语句,这个问题解决方案

  1. package com.weishiyao.learn.day8.singleton.ep3;
  2.  
  3. public class MyObject {
  4. private static MyObject myObject;
  5.  
  6. private MyObject() {
  7.  
  8. }
  9.  
  10. public static MyObject getInstance() {
  11. // 延迟加载
  12. try {
  13. if (myObject != null) {
  14.  
  15. } else {
  16. // 模拟在创建对象之前做一些准备性的工作
  17. Thread.sleep(2000);
  18. synchronized (MyObject.class) {
  19. if (myObject == null) {
  20. myObject = new MyObject();
  21. }
  22. }
  23. }
  24.  
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. return myObject;
  29. }
  30. }

只需要在锁里面再添加一个判断,就可以保证单例了,这个是DCL双检查机制

结果如下:

  1. 1224717057
  2. 1224717057
  3. 1224717057
  4. 1224717057
  5. 1224717057

三、使用内置静态类实现单例

主要代码

  1. package com.weishiyao.learn.day8.singleton.ep4;
  2.  
  3. public class MyObject {
  4. // 内部类方式
  5. private static class MyObjectHandler {
  6. private static MyObject myObject = new MyObject();
  7. }
  8.  
  9. public MyObject() {
  10. }
  11.  
  12. public static MyObject getInstance() {
  13. return MyObjectHandler.myObject;
  14. }
  15. }

线程类代码

  1. package com.weishiyao.learn.day8.singleton.ep4;
  2.  
  3. public class MyThread extends Thread {
  4. @Override
  5. public void run() {
  6. System.out.println(MyObject.getInstance().hashCode());
  7. }
  8. }

运行类

  1. package com.weishiyao.learn.day8.singleton.ep4;
  2.  
  3. public class Run {
  4. public static void main(String[] args) {
  5. MyThread t1 = new MyThread();
  6. MyThread t2 = new MyThread();
  7. MyThread t3 = new MyThread();
  8. MyThread t4 = new MyThread();
  9. MyThread t5 = new MyThread();
  10. t1.start();
  11. t2.start();
  12. t3.start();
  13. t4.start();
  14. t5.start();
  15. }
  16. }

结果

  1. 1851889404
  2. 1851889404
  3. 1851889404
  4. 1851889404
  5. 1851889404

通过内部静态类,得到了线程安全的单例模式

四、序列化和反序列化单例模式

内置静态类可以达到线程安全的问题,但如果遇到序列化对象时,使用默认方式得到的结果还是多例的

MyObject代码

  1. package com.weishiyao.learn.day8.singleton.ep5;
  2.  
  3. import java.io.Serializable;
  4.  
  5. public class MyObject implements Serializable {
  6.  
  7. /**
  8. *
  9. */
  10. private static final long serialVersionUID = 888L;
  11.  
  12. // 内部类方式
  13. private static class MyObjectHandler {
  14. private static MyObject myObject = new MyObject();
  15. }
  16.  
  17. public MyObject() {
  18. }
  19.  
  20. public static MyObject getInstance() {
  21. return MyObjectHandler.myObject;
  22. }
  23.  
  24. // protected MyObject readResolve() {
  25. // System.out.println("调用了readResolve方法!");
  26. // return MyObjectHandler.myObject;
  27. // }
  28. }

业务类

  1. package com.weishiyao.learn.day8.singleton.ep5;
  2.  
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileNotFoundException;
  6. import java.io.FileOutputStream;
  7. import java.io.IOException;
  8. import java.io.ObjectInputStream;
  9. import java.io.ObjectOutputStream;
  10.  
  11. public class SaveAndRead {
  12. public static void main(String[] args) {
  13. try {
  14. MyObject myObject = MyObject.getInstance();
  15. FileOutputStream fosRef = new FileOutputStream(new File("myObjectFile.txt"));
  16. ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
  17. oosRef.writeObject(myObject);
  18. oosRef.close();
  19. fosRef.close();
  20. System.out.println(myObject.hashCode());
  21. } catch (FileNotFoundException e) {
  22. e.printStackTrace();
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. }
  26. FileInputStream fisRef;
  27. try {
  28. fisRef = new FileInputStream(new File("myObjectFile.txt"));
  29. ObjectInputStream iosRef = new ObjectInputStream(fisRef);
  30. MyObject myObject = (MyObject) iosRef.readObject();
  31. iosRef.close();
  32. fisRef.close();
  33. System.out.println(myObject.hashCode());
  34. } catch (FileNotFoundException e) {
  35. e.printStackTrace();
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. } catch (ClassNotFoundException e) {
  39. e.printStackTrace();
  40. }
  41.  
  42. }
  43. }

结果

  1. 970928725
  2. 1099149023

两个不同的hashCode,证明并不是同一个对象,解决方案,添加下面这段代码

  1.   protected MyObject readResolve() {
  2. System.out.println("调用了readResolve方法!");
  3. return MyObjectHandler.myObject;
  4. }

在反序列化的时候调用,可以得到同一个对象

  1. System.out.println(myObject.readResolve().hashCode());

结果

  1. 1255301379
  2. 调用了readResolve方法!
  3. 1255301379

相同的hashCode,证明得到了同一个对象

五、使用static代码块实现单例

静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码快这个特性来实现单利模式

MyObject类

  1. package com.weishiyao.learn.day8.singleton.ep6;
  2.  
  3. public class MyObject {
  4. private static MyObject instance = null;
  5.  
  6. private MyObject() {
  7. super();
  8. }
  9.  
  10. static {
  11. instance = new MyObject();
  12. }
  13.  
  14. public static MyObject getInstance() {
  15. return instance;
  16. }
  17. }

线程类

  1. package com.weishiyao.learn.day8.singleton.ep6;
  2.  
  3. public class MyThread extends Thread {
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 5; i++) {
  7. System.out.println(MyObject.getInstance().hashCode());
  8. }
  9. }
  10. }

运行类

  1. package com.weishiyao.learn.day8.singleton.ep6;
  2.  
  3. public class Run {
  4. public static void main(String[] args) {
  5. MyThread t1 = new MyThread();
  6. MyThread t2 = new MyThread();
  7. MyThread t3 = new MyThread();
  8. MyThread t4 = new MyThread();
  9. MyThread t5 = new MyThread();
  10. t1.start();
  11. t2.start();
  12. t3.start();
  13. t4.start();
  14. t5.start();
  15. }
  16. }

运行结果:

  1. 1678885403
  2. 1678885403
  3. 1678885403
  4. 1678885403
  5. 1678885403
  6. 1678885403
  7. 1678885403
  8. 1678885403
  9. 1678885403
  10. 1678885403
  11. 1678885403
  12. 1678885403
  13. 1678885403
  14. 1678885403
  15. 1678885403
  16. 1678885403
  17. 1678885403
  18. 1678885403
  19. 1678885403
  20. 1678885403
  21. 1678885403
  22. 1678885403
  23. 1678885403
  24. 1678885403
  25. 1678885403

通过静态代码块只执行一次的特性也成功的得到了线程安全的单例模式

六、使用enum枚举数据类型实现单例模式

枚举enum和静态代码块的特性类似,在使用枚举时,构造方法会被自动调用,也可以用来实现单例模式

MyObject类

  1. package com.weishiyao.learn.day8.singleton.ep7;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.SQLException;
  6.  
  7. public enum MyObject {
  8. connectionFactory;
  9.  
  10. private Connection connection;
  11.  
  12. private MyObject() {
  13. try {
  14. System.out.println("调用了MyObject的构造");
  15. String url = "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8";
  16. String name = "root";
  17. String password = "111111";
  18. String driverName = "com.mysql.jdbc.Driver";
  19. Class.forName(driverName);
  20. connection = DriverManager.getConnection(url, name, password);
  21. } catch (ClassNotFoundException e) {
  22. e.printStackTrace();
  23. } catch (SQLException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27.  
  28. public Connection getConnection() {
  29. return connection;
  30. }
  31. }

线程类

  1. package com.weishiyao.learn.day8.singleton.ep7;
  2.  
  3. public class MyThread extends Thread {
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 5; i++) {
  7. System.out.println(MyObject.connectionFactory.getConnection().hashCode());
  8. }
  9. }
  10. }

运行类

  1. package com.weishiyao.learn.day8.singleton.ep7;
  2.  
  3. public class Run {
  4. public static void main(String[] args) {
  5. MyThread t1 = new MyThread();
  6. MyThread t2 = new MyThread();
  7. MyThread t3 = new MyThread();
  8. MyThread t4 = new MyThread();
  9. MyThread t5 = new MyThread();
  10. t1.start();
  11. t2.start();
  12. t3.start();
  13. t4.start();
  14. t5.start();
  15. }
  16. }

运行结果

  1. 调用了MyObject的构造
  2. 56823666
  3. 56823666
  4. 56823666
  5. 56823666
  6. 56823666
  7. 56823666
  8. 56823666
  9. 56823666
  10. 56823666
  11. 56823666
  12. 56823666
  13. 56823666
  14. 56823666
  15. 56823666
  16. 56823666
  17. 56823666
  18. 56823666
  19. 56823666
  20. 56823666
  21. 56823666
  22. 56823666
  23. 56823666
  24. 56823666
  25. 56823666
  26. 56823666

上面这种写法将枚举类暴露了,违反了“职责单一原则”,可以使用一个类将枚举包裹起来

  1. package com.weishiyao.learn.day8.singleton.ep8;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.SQLException;
  6.  
  7. public class MyObject {
  8.  
  9. public enum MyEnumSingleton {
  10. connectionFactory;
  11.  
  12. private Connection connection;
  13.  
  14. private MyEnumSingleton() {
  15. try {
  16. System.out.println("调用了MyObject的构造");
  17. String url = "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8";
  18. String name = "root";
  19. String password = "111111";
  20. String driverName = "com.mysql.jdbc.Driver";
  21. Class.forName(driverName);
  22. connection = DriverManager.getConnection(url, name, password);
  23. } catch (ClassNotFoundException e) {
  24. e.printStackTrace();
  25. } catch (SQLException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29.  
  30. public Connection getConnection() {
  31. return connection;
  32. }
  33. }
  34.  
  35. public static Connection getConnection() {
  36. return MyEnumSingleton.connectionFactory.getConnection();
  37. }
  38. }

更改线程代码

  1. package com.weishiyao.learn.day8.singleton.ep8;
  2.  
  3. public class MyThread extends Thread {
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 5; i++) {
  7. System.out.println(MyObject.getConnection().hashCode());
  8. }
  9. }
  10. }

结果

  1. 调用了MyObject的构造
  2. 1948356121
  3. 1948356121
  4. 1948356121
  5. 1948356121
  6. 1948356121
  7. 1948356121
  8. 1948356121
  9. 1948356121
  10. 1948356121
  11. 1948356121
  12. 1948356121
  13. 1948356121
  14. 1948356121
  15. 1948356121
  16. 1948356121
  17. 1948356121
  18. 1948356121
  19. 1948356121
  20. 1948356121
  21. 1948356121
  22. 1948356121
  23. 1948356121
  24. 1948356121
  25. 1948356121
  26. 1948356121

以上总结了单利模式与多线程结合时遇到的各种情况和解决方案,以供以后使用时查阅。

java多线程(一)——线程安全的单例模式的更多相关文章

  1. Java多线程之线程其他类

    Java多线程之线程其他类 实际编码中除了前面讲到的常用的类之外,还有几个其他类也有可能用得到,这里来统一整理一下: 1,Callable接口和Future接口 JDK1.5以后提供了上面这2个接口, ...

  2. Java多线程之线程的通信

    Java多线程之线程的通信 在总结多线程通信前先介绍一个概念:锁池.线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池.每个对象都有自己的锁池的空间,用于放置等待运行的线程.这些 ...

  3. Java多线程之线程的同步

    Java多线程之线程的同步 实际开发中我们也经常提到说线程安全问题,那么什么是线程安全问题呢? 线程不安全就是说在多线程编程中出现了错误情况,由于系统的线程调度具有一定的随机性,当使用多个线程来访问同 ...

  4. Java多线程之线程的控制

    Java多线程之线程的控制 线程中的7 种非常重要的状态:  初始New.可运行Runnable.运行Running.阻塞Blocked.锁池lock_pool.等待队列wait_pool.结束Dea ...

  5. Java多线程父子线程关系 多线程中篇(六)

    有的时候对于Java多线程,我们会听到“父线程.子线程”的概念. 严格的说,Java中不存在实质上的父子关系 没有方法可以获取一个线程的父线程,也没有方法可以获取一个线程所有的子线程 子线程的消亡与父 ...

  6. 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

    Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...

  7. Java多线程02(线程安全、线程同步、等待唤醒机制)

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

  8. JAVA多线程之线程间的通信方式

    (转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...

  9. java多线程与线程间通信

    转自(http://blog.csdn.net/jerrying0203/article/details/45563947) 本文学习并总结java多线程与线程间通信的原理和方法,内容涉及java线程 ...

随机推荐

  1. 【总结】编写自己的JDBC框架

    一.数据库连接池: 在一般用JDBC 进行连接数据库进行CRUD操作时,每一次都会: 通过:java.sql.Connection conn = DriverManager.getConnection ...

  2. TP-Link路由器刷dd-wrt的linux,无线信号增强不少

    家里面TP-LINK WR841ND v3老路由器各方面都不错,双天线,300M,用了好几年了,但摆在客厅里面,最远的卧室处无线就不是很稳定了,想了各种增强解决办法: 1.更换天线.        一 ...

  3. 随手写的自动批量编译部署NativeAndroid程序Python脚本

    背景 有一堆工程NativeAndroid程序,要一一编译部署编译测试,手头只有AndroidManifest和Makefile,需要一个个Update,Ndk-build,和发包安装测试,很是头疼, ...

  4. Unity3d插件汇总

    Unity3d 中的svn插件 插件下载地址:http://www.dehome.net/down/viewfile.php?file_id=53

  5. IOS的启动画面的适配问题

    iPhone4,iPhone4s 分辨率960*640 长宽比1.5iPhone5,iPhone5s 分辨率1136*640 长宽比1.775iPhone6 分辨率1334*750 长宽比1.778i ...

  6. Using the Cordova Camera API

    使用ionic开发一款android或ios应用,估计少不了使用到Camera API,这里记录下使用过程. 创建空的ionic应用 ionic start myTabs tabs 通过cd demo ...

  7. Android 优化布局层次结构

    前面介绍过使用HierarchyViewer和Android lint来优化我们的程序,这一篇算是总结性的,借助一个小例子来说用怎么优化应用布局.这个例子是android官网给出的,作者也当一把翻译. ...

  8. [转]make: 警告:检测到时钟错误。您的创建可能是不完整的。

    转自http://blog.csdn.net/maopig/article/details/6599660 我在make的时候也出现了同样的问题,不过不是什么大问题,这个不影响编译结果 分析原因可能是 ...

  9. windows下远程连接ubantu

    要实现windows下连接ubantu需要安装以下软件: 1. windows下安装winSCP 2. Ubantu下安装OpenSSH Server 可以使用命令行安装openSSH Server: ...

  10. jQuery 1.9 Ajax代码带注释

    /* -----------ajax模块开始 -----------*/ var // Document location ajaxLocParts, ajaxLocation, ajax_nonce ...