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

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

(0)

相关推荐

  • java单例模式4种使用方式分享

    1.Java Concurrency In Practice的List 复制代码 代码如下: public class Singleton {      private static class SingletonHolder {          public static Singleton resource = new Singleton();      }      public static Singleton getResource() {          return  Sing

  • java设计模式之单例模式学习

    1 概述 单例模式有几个好处: (1)某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销. (2)省去了new操作符,降低了系统内存的使用频率,减轻GC压力. (3)有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了. 2 详解 单例模式常用的写法有如下这么两种. 2.1 饿汉式 如果应用程序总是创建并使用单例模式,或者在创建和运行时压力不是很大的情况下,可以使用一个私有静态变量,提前把对象创建好. 复制代码 代码如下: package org.sc

  • java 单例模式(饿汉模式与懒汉模式)

    java 单例模式 饿汉式单例 对于饿汉模式,我们可这样理解:该单例类非常饿,迫切需要吃东西,所以它在类加载的时候就立即创建对象. 懒汉式单例类 对于懒汉模式,我们可以这样理解:该单例类非常懒,只有在自身需要的时候才会行动,从来不知道及早做好准备.它在需要对象的时候,才判断是否已有对象,如果没有就立即创建一个对象,然后返回,如果已有对象就不再创建,立即返回. 单例设计模式常用于JDBC链接数据库 注意: 1 我们常用的是第一种饿汉式,因为: (1)既然采用了单例设计模式,就是为了使用单例类的对象

  • 浅析Java设计模式编程中的单例模式和简单工厂模式

    单例模式 动机 有时候只有一个类的实例是很重要的.比如,一个系统应该只有一个窗口管理实例. 单例模式是最简单设计模式:类负责实例化自己,确保只有一个实例,并且提供一个访问这个实例的入口. 目的 1. 确保只有一个实例被创建. 2. 提供访问这个实例的入口. 使用final确保被创建一次,private的构造函数确保不被实例化.public的getInstance方法确保外部能够访问.下面是饿汉模式: public class Singleton { private static final Si

  • java单例模式实现面板切换

    本文实例为大家分享了java单例模式实现面板切换的具体代码,供大家参考,具体内容如下 1.首先介绍一下什么是单例模式: java单例模式是一种常见的设计模式,那么我们先看看懒汉模式: public class Singleton_ { //设为私有方法,防止被外部类引用或实例 private Singleton_(){ System.out.println("懒汉单例模式"); } private static Singleton_ single = null; //并对外只暴露get

  • java单例模式学习示例

    单例模式有一下特点:1.单例类只能有一个实例.2.单例类必须自己自己创建自己的唯一实例.3.单例类必须给所有其他对象提供这一实例.单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器的功能.每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中.每台计算机可以有若干通信端口,系统应当集中管理这些通信端

  • Java单例模式的应用示例

    单例模式用于保证在程序的运行期间某个类有且仅有一个实例.其优势在于尽可能解决系统资源.通过修改构造方法的访问权限就可以实现单例模式. 代码如下: 复制代码 代码如下: public class Emperor {    private static Emperor emperor = null;// 声明一个Emperor类的引用 private Emperor() {// 将构造方法私有    } public static Emperor getInstance() {// 实例化引用   

  • java单例模式使用及注意事项

    1. 说明 1)单例模式:确保一个类只有一个实例,自行实例化并向系统提供这个实例 2)单例模式分类:饿单例模式(类加载时实例化一个对象给自己的引用),懒单例模式(调用取得实例的方法如getInstance时才会实例化对象)(java中饿单例模式性能优于懒单例模式,c++中一般使用懒单例模式) 3)单例模式要素: a)私有构造方法b)私有静态引用指向自己实例c)以自己实例为返回值的公有静态方法 2.实例 饿单例模式: 复制代码 代码如下: package com.wish.modedesign;

  • JAVA实现单例模式的四种方法和一些特点

    一.饿汉式单例类 复制代码 代码如下: public class Singleton  {      private Singleton(){ } private static Singleton instance = new Singleton(); private static Singleton getInstance(){          return instance;      }  } 特点:饿汉式提前实例化,没有懒汉式中多线程问题,但不管我们是不是调用getInstance()

  • Java单例模式、饥饿模式代码实例

    class MyThreadScopeData {       // 单例     private MyThreadScopeData() {     }       // 提供获取实例方法     public static synchronized MyThreadScopeData getThreadInstance() {         // 从当前线程范围内数据集中获取实例对象         MyThreadScopeData instance = map.get();      

随机推荐