ホームページ  >  記事  >  Java  >  Java マルチスレッドのスレッドセーフなシングルトン モード

Java マルチスレッドのスレッドセーフなシングルトン モード

高洛峰
高洛峰オリジナル
2017-01-05 16:48:231351ブラウズ

概念:
Java のシングルトン パターンは、一般的な設計パターンです。シングルトン パターンには、遅延スタイルのシングルトン、ハングリースタイルのシングルトン、および登録スタイルのシングルトンの 3 種類があります。
シングルトンパターンには以下の特徴があります:
1. シングルトンクラスはインスタンスを 1 つだけ持つことができます。
2. シングルトンクラスは独自の一意のインスタンスを作成する必要があります。
3. シングルトンクラスは、このインスタンスを他のすべてのオブジェクトに提供する必要があります。
シングルトンパターンは、クラスがインスタンスを 1 つだけ持つことを保証し、クラス自体をインスタンス化し、このインスタンスをシステム全体に提供します。コンピュータ システムでは、スレッド プール、キャッシュ、ログ オブジェクト、ダイアログ ボックス、プリンタ、グラフィック カード ドライバ オブジェクトはシングルトンとして設計されることがよくあります。これらのアプリケーションはすべて、多かれ少なかれリソース マネージャーの機能を備えています。各コンピュータには複数のプリンタを搭載できますが、2 つの印刷ジョブが同時にプリンタに出力されることを防ぐために、存在できるプリンタ スプーラは 1 つだけです。各コンピュータは複数の通信ポートを持つことができ、システムはこれらの通信ポートを集中管理して、1 つの通信ポートが 2 つの要求によって同時に呼び出されないようにする必要があります。つまり、シングルトン モードを選択する目的は、矛盾した状態を回避し、長期的なポリシーを回避することです。

ここでは主に怠け者の中華風と空腹の中華風の2種類を詳しく紹介します

1. 即時読み込み/空腹の中華風

メソッドを呼び出す前にインスタンスが作成されています。コード:

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は同じ値であり、オブジェクトも同じであることを示し、即時読み込み単一関心モードが実装されていることを示します

2。 style

メソッド呼び出し時 インスタンスは後で作成されます。実装計画では、呼び出し時にのみオブジェクトのインスタンスが作成されるように、インスタンス化を行うことができます。

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());
  }
}

実行結果

167772895

オブジェクトのインスタンスを取り出しますが、マルチスレッド環境だと複数のインスタンスが現れるのでシングルトンモードではありません

テストクラスを実行する

package com.weishiyao.learn.day8.singleton.ep2;
 
public class Run {
  public static void main(String[] args) {
    MyThread t1 = new MyThread();
    t1.start();
  }
}

実行結果

980258163

1224717057

1851889404
188820504
1672864109
問題があるので、遅延モードでマルチスレッドソリューションを解決する必要があります。 :

最初の解決策、最も一般的なのは、 synchronized を追加することであり、 synchronized は別の位置に追加できます

最初のもの、メソッド ロック

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();
  }
}

この同期された同期スキームは効率が低すぎるため、メソッド全体がロックされます

2 番目の同期された使用スキーム

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;
  }
}

このメソッドの効率も非常に低く、キー コードのみをロックする必要があります

パッケージ com.weishiyao.learn.day8.singleton.ep3;

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;
  }
}

は次のように書かれています 最適解のように見えますが、実行してみると、実際にはスレッドセーフではないことがわかりました。 結果:

1224717057

971173439

1851889404

1224717057

1672864109
なぜですか?

オブジェクト作成ステートメントはロックされていますが、一度に作成を完了できるのは 1 つのスレッドだけです。ただし、最初のスレッドが Object オブジェクトを作成するために入っても、2 番目のスレッドは引き続きオブジェクトの作成を続けることができます。作成ステートメントはロックされています。この問題の解決策

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;
  }
}

は、シングルトンを確保するためにロックに別の判定を追加するだけです。これが DCL の二重チェック メカニズムです

結果は次のとおりです:

1224717057

1224717057

1224717057

1224717057

3. 組み込み静的クラスを使用してシングルトンを実装します

メインコード

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;
  }
}

スレッドクラスコード
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());
  }
}

結果

1851889404

1851889404

1851889404

1851889404

1851889404

内部静的クラスでは、スレッドセーフなシングルトン モードが得られます

4. シリアル化と逆シリアル化のシングルトン モード

組み込みの静的クラスはスレッド セーフの問題を解決できますが、シリアル化されたオブジェクトが発生した場合、デフォルトを使用して結果が取得されます。メソッドは依然として複数のインスタンスです。

MyObject コード

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();
  }
}

Business class

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;
//  }
}

結果

970928725

1099149023

解決策は、次のコードを追加することです

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();
    }
     
     
  }
}

逆シリアル化呼び出し中に、同じオブジェクトを取得できます


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

1255301379

readResolve メソッドが呼び出されました!

1255301379
同じハッシュコードは同じオブジェクトが取得されたことを証明します

5. 静的コードブロックを使用してシングルトンを実装します

クラスを使用するときに静的コードブロック内のコードはすでに実行されているため、静的コード機能を適用できますシングルプロフィットモードを実現するには

MyObjectクラス

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

Threadクラス

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());
    }
  }
}

実行結果:

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

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

更多java多线程之线程安全的单例模式相关文章请关注PHP中文网!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。