>  기사  >  Java  >  Java 멀티스레딩의 스레드로부터 안전한 싱글톤 모드

Java 멀티스레딩의 스레드로부터 안전한 싱글톤 모드

高洛峰
高洛峰원래의
2017-01-05 16:48:231350검색

개념:
싱글톤 패턴은 Java의 일반적인 디자인 패턴으로 게으른 스타일 싱글톤, 배고픈 스타일 싱글톤, 등록 스타일 싱글톤의 세 가지 유형이 있습니다.
싱글턴 모델의 특징은 다음과 같습니다.
1. 싱글턴 클래스는 인스턴스를 하나만 가질 수 있습니다.
 2. 싱글톤 클래스는 고유한 인스턴스를 생성해야 합니다.
 3. 싱글톤 클래스는 이 인스턴스를 다른 모든 객체에 제공해야 합니다.
싱글톤 패턴은 클래스에 인스턴스가 하나만 있도록 보장하고 자체적으로 인스턴스화하며 이 인스턴스를 전체 시스템에 제공합니다. 컴퓨터 시스템에서 스레드 풀, 캐시, 로그 개체, 대화 상자, 프린터 및 그래픽 카드 드라이버 개체는 종종 싱글톤으로 설계됩니다. 이러한 응용 프로그램은 모두 리소스 관리자의 기능을 어느 정도 갖추고 있습니다. 각 컴퓨터에는 여러 대의 프린터가 있을 수 있지만 두 개의 인쇄 작업이 동시에 프린터로 출력되는 것을 방지하기 위해 프린터 스풀러는 하나만 있을 수 있습니다. 각 컴퓨터에는 여러 개의 통신 포트가 있을 수 있으며, 하나의 통신 포트가 동시에 두 개의 요청에 의해 호출되는 것을 방지하기 위해 시스템은 이러한 통신 포트를 중앙에서 관리해야 합니다. 간단히 말해서, 싱글톤 모드를 선택하는 목적은 일관성 없는 상태를 피하고 장기적인 정책을 피하는 것입니다.

여기에서는 주로 게으른 중국 스타일과 배고픈 중국 스타일의 두 가지 유형을 자세히 소개합니다.

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
해시코드 값이 동일하여 객체도 동일함을 나타내며 즉시 로딩 단일 관심 모드가 구현되었음을 나타냅니다.

2. 레이지 로딩/lazy 스타일

메서드가 호출될 때까지 인스턴스가 나타나지 않습니다. 구현 솔루션은 인스턴스화를 매개변수 없는 생성자에 넣어 호출할 때만 객체의 인스턴스가 생성되도록 하는 것입니다. >

스레드 클래스 생성
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
문제가 있으므로 문제를 해결해야 합니다. 코드:

가장 일반적인 첫 번째 해결 방법은 동기화를 추가하는 것이며, 동기화는 다른 위치에 추가할 수 있습니다

첫 번째 방법 잠금

이것은 동기화 동기화 방식은 효율성이 너무 낮고 전체 방법이 잠겨 있습니다
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;
  }
}

두 번째 동기화 사용 계획

이 방법도 매우 비효율적입니다. 3차 동기화 사용법
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;
  }
}
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
왜죠?

객체 생성 문이 잠겨 있어도 한 번에 하나의 스레드만 생성을 완료할 수 있지만, 첫 번째 스레드가 Object 객체를 생성하려고 들어와도 두 번째 스레드가 계속 생성할 수 있기 때문입니다. create 문만 잠그는데, 이 문제에 대한 해결책은

잠금에 또 다른 판단을 추가하여 싱글톤을 보장하는 것입니다. 이것이 바로 DCL 이중 확인 메커니즘입니다
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;
  }
}

결과입니다.

1224717057

1224717057

1224717057
1224717057
1224717057
3. 내장된 정적 클래스를 사용하여 싱글턴 구현

코드

스레드 클래스 코드
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
내부 정적 클래스를 통해 스레드로부터 안전한 싱글톤 모드를 얻습니다

4. 직렬화 및 역직렬화 싱글톤 모드

내장된 정적 클래스가 스레드에 도달할 수 있는 보안입니다. 문제가 발생하지만 직렬화된 개체가 발생하면 기본 방법을 사용하여 얻은 결과는 여전히 여러 인스턴스

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

Result


1255301379

readResolve 메소드가 호출됩니다!

1255301379
동일한 hashCode는 동일한 객체를 얻었음을 증명합니다

5. 정적 코드 블록을 사용하여 싱글턴 구현

사용 시 정적 코드 블록의 코드가 변경됩니다. 클래스가 실행되었으므로 정적 코드 기능을 사용하여 단일 관심 모드

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

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

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


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.