java多线程之线程安全的单例模式


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

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

一、立即加载/饿汉式

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



package          com.weishiyao.learn.day8.singleton.ep1;         


                    


          public          class          MyObject {         


                    // 立即加载方式==恶汉模式         


                    private          static          MyObject myObject =           new          MyObject();         


                    


                    private          MyObject() {         


                    }         


                    


                    public          static          MyObject getInstance() {         


                    // 此代码版本为立即加载         


                    // 此版本代码的缺点是不能有其他实例变量         


                    // 因为getInstance()方法没有同步         


                    // 所以有可能出现非线程安全的问题         


                    return          myObject;         


                    }         


          }



创建线程类





package          com.weishiyao.learn.day8.singleton.ep1;         


                    


          public          class          MyThread           extends          Thread {         


                    @Override         


                    public          void          run() {         


                    System.out.println(MyObject.getInstance().hashCode());         


                    }         


          }


创建运行类


?



package          com.weishiyao.learn.day8.singleton.ep1;         


                    


          public          class          Run {         


                    public          static          void          main(String[] args) {         


                    MyThread t1 =           new          MyThread();         


                    MyThread t2 =           new          MyThread();         


                    MyThread t3 =           new          MyThread();         


                    t1.start();         


                    t2.start();         


                    t3.start();         


                    }         


          }


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

二、延迟加载/懒汉式

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


?




package          com.weishiyao.learn.day8.singleton.ep2;         


                    


          public          class          MyObject {         


                    private          static          MyObject myObject;         


                    


                    private          MyObject() {         


                    


                    }         


                    


                    public          static          MyObject getInstance() {         


                    // 延迟加载         


                    if          (myObject !=           null          ) {         


                    


                    }           else          {         


                    myObject =           new          MyObject();         


                    }         


                    return          myObject;         


                    }         


          }


创建线程类


?



package          com.weishiyao.learn.day8.singleton.ep2;         


                    


          public          class          MyThread           extends          Thread {         


                    @Override         


                    public          void          run() {         


                    System.out.println(MyObject.getInstance().hashCode());         


                    }         


          }


创建运行类


?




package          com.weishiyao.learn.day8.singleton.ep2;         


                    


          public          class          Run {         


                    public          static          void          main(String[] args) {         


                    MyThread t1 =           new          MyThread();         


                    t1.start();         


                    }         


          }


运行结果

167772895

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

运行测试类


?




package          com.weishiyao.learn.day8.singleton.ep2;         


                    


          public          class          Run {         


                    public          static          void          main(String[] args) {         


                    MyThread t1 =           new          MyThread();         


                    MyThread t2 =           new          MyThread();         


                    MyThread t3 =           new          MyThread();         


                    MyThread t4 =           new          MyThread();         


                    MyThread t5 =           new          MyThread();         


                    t1.start();         


                    t2.start();         


                    t3.start();         


                    t4.start();         


                    t5.start();         


                    }         


          }


运行结果

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

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

第一种,方法锁


?




package          com.weishiyao.learn.day8.singleton.ep3;         


                    


          public          class          MyObject {         


                    private          static          MyObject myObject;         


                    


                    private          MyObject() {         


                    


                    }         


                    


                    synchronized          public          static          MyObject getInstance() {         


                    // 延迟加载         


                    try          {         


                    if          (myObject !=           null          ) {         


                    


                    }           else          {         


                    // 模拟在创建对象之前做一些准备性的工作         


                    Thread.sleep(          2000          );         


                    myObject =           new          MyObject();         


                    }         


                    


                    }           catch          (InterruptedException e) {         


                    e.printStackTrace();         


                    }         


                    return          myObject;         


                    }         


          }



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

第二种synchronized使用方案


?




package          com.weishiyao.learn.day8.singleton.ep3;         


                    


          public          class          MyObject {         


                    private          static          MyObject myObject;         


                    


                    private          MyObject() {         


                    


                    }         


                    


                    public          static          MyObject getInstance() {         


                    // 延迟加载         


                    try          {         


                    synchronized          (MyObject.          class          ) {         


                    if          (myObject !=           null          ) {         


                    


                    }           else          {         


                    // 模拟在创建对象之前做一些准备性的工作         


                    Thread.sleep(          2000          );         


                    myObject =           new          MyObject();         


                    }         


                    }         


                    


                    


                    }           catch          (InterruptedException e) {         


                    e.printStackTrace();         


                    }         


                    return          myObject;         


                    }         


          }


这种方法效率一样很低,方法内的所有代码都被锁住,只需要锁住关键代码就好,第三种synchronized使用方案
package com.weishiyao.learn.day8.singleton.ep3;


?



public          class          MyObject {         


                    private          static          MyObject myObject;         


                    


                    private          MyObject() {         


                    


                    }         


                    


                    public          static          MyObject getInstance() {         


                    // 延迟加载         


                    try          {         


                    if          (myObject !=           null          ) {         


                    


                    }           else          {         


                    // 模拟在创建对象之前做一些准备性的工作         


                    Thread.sleep(          2000          );         


                    synchronized          (MyObject.          class          ) {         


                    myObject =           new          MyObject();         


                    }         


                    }         


                    


                    


                    }           catch          (InterruptedException e) {         


                    e.printStackTrace();         


                    }         


                    return          myObject;         


                    }         


          }


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

结果:

1224717057
971173439
1851889404
1224717057
1672864109
Why?

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


?



package          com.weishiyao.learn.day8.singleton.ep3;         


                    


          public          class          MyObject {         


                    private          static          MyObject myObject;         


                    


                    private          MyObject() {         


                    


                    }         


                    


                    public          static          MyObject getInstance() {         


                    // 延迟加载         


                    try          {         


                    if          (myObject !=           null          ) {         


                    


                    }           else          {         


                    // 模拟在创建对象之前做一些准备性的工作         


                    Thread.sleep(          2000          );         


                    synchronized          (MyObject.          class          ) {         


                    if          (myObject ==           null          ) {         


                    myObject =           new          MyObject();         


                    }         


                    }         


                    }         


                    


                    


                    }           catch          (InterruptedException e) {         


                    e.printStackTrace();         


                    }         


                    return          myObject;         


                    }         


          }


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

结果如下:

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

主要代码


?



package          com.weishiyao.learn.day8.singleton.ep4;         


                    


          public          class          MyObject {         


                    // 内部类方式         


                    private          static          class          MyObjectHandler {         


                    private          static          MyObject myObject =           new          MyObject();         


                    }         


                    


                    public          MyObject() {         


                    }         


                    


                    public          static          MyObject getInstance() {         


                    return          MyObjectHandler.myObject;         


                    }         


          }



线程类代码


?




package          com.weishiyao.learn.day8.singleton.ep4;         


                    


          public          class          MyThread           extends          Thread {         


                    @Override         


                    public          void          run() {         


                    System.out.println(MyObject.getInstance().hashCode());         


                    }         


          }


运行类


?



package          com.weishiyao.learn.day8.singleton.ep4;         


                    


          public          class          Run {         


                    public          static          void          main(String[] args) {         


                    MyThread t1 =           new          MyThread();         


                    MyThread t2 =           new          MyThread();         


                    MyThread t3 =           new          MyThread();         


                    MyThread t4 =           new          MyThread();         


                    MyThread t5 =           new          MyThread();         


                    t1.start();         


                    t2.start();         


                    t3.start();         


                    t4.start();         


                    t5.start();         


                    }         


          }


结果

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

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

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

MyObject代码


?



package          com.weishiyao.learn.day8.singleton.ep5;         


                    


          import          java.io.Serializable;         


                    


          public          class          MyObject           implements          Serializable {         


                    


                    /**         


                    *          


                    */         


                    private          static          final          long          serialVersionUID = 888L;         


                    


                    // 内部类方式         


                    private          static          class          MyObjectHandler {         


                    private          static          MyObject myObject =           new          MyObject();         


                    }         


                    


                    public          MyObject() {         


                    }         


                    


                    public          static          MyObject getInstance() {         


                    return          MyObjectHandler.myObject;         


                    }         


                    


          //  protected MyObject readResolve() {         


          //    System.out.println("调用了readResolve方法!");         


          //    return MyObjectHandler.myObject;         


          //  }         


          }


业务类


?




package          com.weishiyao.learn.day8.singleton.ep5;         


                    


          import          java.io.File;         


          import          java.io.FileInputStream;         


          import          java.io.FileNotFoundException;         


          import          java.io.FileOutputStream;         


          import          java.io.IOException;         


          import          java.io.ObjectInputStream;         


          import          java.io.ObjectOutputStream;         


                    


          public          class          SaveAndRead {         


                    public          static          void          main(String[] args) {         


                    try          {         


                    MyObject myObject = MyObject.getInstance();         


                    FileOutputStream fosRef =           new          FileOutputStream(          new          File(          "myObjectFile.txt"          ));         


                    ObjectOutputStream oosRef =           new          ObjectOutputStream(fosRef);         


                    oosRef.writeObject(myObject);         


                    oosRef.close();         


                    fosRef.close();         


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


                    }           catch          (FileNotFoundException e) {         


                    e.printStackTrace();         


                    }           catch          (IOException e) {         


                    e.printStackTrace();         


                    }         


                    FileInputStream fisRef;         


                    try          {         


                    fisRef =           new          FileInputStream(          new          File(          "myObjectFile.txt"          ));         


                    ObjectInputStream iosRef =           new          ObjectInputStream(fisRef);         


                    MyObject myObject = (MyObject) iosRef.readObject();         


                    iosRef.close();         


                    fisRef.close();         


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


                    }           catch          (FileNotFoundException e) {         


                    e.printStackTrace();         


                    }           catch          (IOException e) {         


                    e.printStackTrace();         


                    }           catch          (ClassNotFoundException e) {         


                    e.printStackTrace();         


                    }         


                    


                    


                    }         


          }


结果

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


?



protected          MyObject readResolve() {         


                    System.out.println(          "调用了readResolve方法!"          );         


                    return          MyObjectHandler.myObject;         


                    }


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

 System.out.println(myObject.readResolve().hashCode());结果

 1255301379
 调用了readResolve方法!
 1255301379
相同的hashCode,证明得到了同一个对象

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

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

MyObject类


?




package          com.weishiyao.learn.day8.singleton.ep6;         


                    


          public          class          MyObject {         


                    private          static          MyObject instance =           null          ;         


                    


                    private          MyObject() {         


                    super          ();         


                    }         


                    


                    static          {         


                    instance =           new          MyObject();         


                    }         


                    


                    public          static          MyObject getInstance() {         


                    return          instance;         


                    }         


          }


线程类


?



package          com.weishiyao.learn.day8.singleton.ep6;         


                    


          public          class          MyThread           extends          Thread {         


                    @Override         


                    public          void          run() {         


                    for          (          int          i =           0          ; i <           5          ; i++) {         


                    System.out.println(MyObject.getInstance().hashCode());         


                    }         


                    }         


          }


运行类


?




package          com.weishiyao.learn.day8.singleton.ep6;         


                    


          public          class          Run {         


                    public          static          void          main(String[] args) {         


                    MyThread t1 =           new          MyThread();         


                    MyThread t2 =           new          MyThread();         


                    MyThread t3 =           new          MyThread();         


                    MyThread t4 =           new          MyThread();         


                    MyThread t5 =           new          MyThread();         


                    t1.start();         


                    t2.start();         


                    t3.start();         


                    t4.start();         


                    t5.start();         


                    }         


          }


运行结果:

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

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

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

MyObject类


?




package          com.weishiyao.learn.day8.singleton.ep7;         


                    


          import          java.sql.Connection;         


          import          java.sql.DriverManager;         


          import          java.sql.SQLException;         


                    


                    


          public          enum          MyObject {         


                    connectionFactory;         


                    


                    private          Connection connection;         


                    


                    private          MyObject() {         


                    try          {         


                    System.out.println(          "调用了MyObject的构造"          );         


                    String url =           "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8"          ;         


                    String name =           "root"          ;         


                    String password =           "111111"          ;         


                    String driverName =           "com.mysql.jdbc.Driver"          ;         


                    Class.forName(driverName);         


                    connection = DriverManager.getConnection(url, name, password);         


                    }           catch          (ClassNotFoundException e) {         


                    e.printStackTrace();         


                    }           catch          (SQLException e) {         


                    e.printStackTrace();         


                    }         


                    }         


                    


                    public          Connection getConnection() {         


                    return          connection;         


                    }         


          }


线程类


?




package          com.weishiyao.learn.day8.singleton.ep7;         


                    


          public          class          MyThread           extends          Thread {         


                    @Override         


                    public          void          run() {         


                    for          (          int          i =           0          ; i <           5          ; i++) {         


                    System.out.println(MyObject.connectionFactory.getConnection().hashCode());         


                    }         


                    }         


          }


运行类


?




package          com.weishiyao.learn.day8.singleton.ep7;         


                    


          public          class          Run {         


                    public          static          void          main(String[] args) {         


                    MyThread t1 =           new          MyThread();         


                    MyThread t2 =           new          MyThread();         


                    MyThread t3 =           new          MyThread();         


                    MyThread t4 =           new          MyThread();         


                    MyThread t5 =           new          MyThread();         


                    t1.start();         


                    t2.start();         


                    t3.start();         


                    t4.start();         


                    t5.start();         


                    }         


          }


运行结果

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


?




package          com.weishiyao.learn.day8.singleton.ep8;         


                    


          import          java.sql.Connection;         


          import          java.sql.DriverManager;         


          import          java.sql.SQLException;         


                    


                    


          public          class          MyObject {         


                    


                    public          enum          MyEnumSingleton {         


                    connectionFactory;         


                    


                    private          Connection connection;         


                    


                    private          MyEnumSingleton() {         


                    try          {         


                    System.out.println(          "调用了MyObject的构造"          );         


                    String url =           "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8"          ;         


                    String name =           "root"          ;         


                    String password =           "111111"          ;         


                    String driverName =           "com.mysql.jdbc.Driver"          ;         


                    Class.forName(driverName);         


                    connection = DriverManager.getConnection(url, name, password);         


                    }           catch          (ClassNotFoundException e) {         


                    e.printStackTrace();         


                    }           catch          (SQLException e) {         


                    e.printStackTrace();         


                    }         


                    }         


                    


                    public          Connection getConnection() {         


                    return          connection;         


                    }         


                    }         


                    


                    public          static          Connection getConnection() {         


                    return          MyEnumSingleton.connectionFactory.getConnection();         


                    }         


          }


更改线程代码


?




package          com.weishiyao.learn.day8.singleton.ep8;         


                    


          public          class          MyThread           extends          Thread {         


                    @Override         


                    public          void          run() {         


                    for          (          int          i =           0          ; i <           5          ; i++) {         


                    System.out.println(MyObject.getConnection().hashCode());         


                    }         


                    }         


          }


结果
调用了MyObject的构造
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121