java 다중 스레드 안전한 단일 모드

개념:
java에서 단례 모델은 흔히 볼 수 있는 디자인 모델로 단례 모델은 세 가지로 나뉘는데 그것이 바로 게으름뱅이 단례, 굶주린 단례, 등록식 단례 세 가지이다.
단례 모드의 특징은 다음과 같다.
1. 단례류는 하나의 실례만 있을 수 있다.
2. 단일 클래스는 자신의 유일한 실례를 만들어야 한다.
3. 단일 클래스는 모든 다른 대상에 이 실례를 제공해야 한다.
단일 모드는 특정한 종류가 하나의 실례만 있고 자체적으로 실례화하여 전체 시스템에 이 실례를 제공한다.컴퓨터 시스템에서 스레드 풀, 캐시, 로그 대상, 대화상자, 프린터, 그래픽 카드의 드라이버 대상은 항상 하나의 예로 설계된다.이런 응용 프로그램들은 모두 많든 적든 자원 관리자의 기능을 가지고 있다.컴퓨터당 몇 개의 프린터가 있을 수 있지만, 두 개의 프린터 작업이 동시에 프린터로 출력되지 않도록 Printer Spooler만 있을 수 있습니다.모든 컴퓨터에는 몇 개의 통신 포트가 있을 수 있으며, 시스템은 하나의 통신 포트가 동시에 두 개의 요청에 의해 호출되는 것을 피하기 위해 이 통신 포트를 집중적으로 관리해야 한다.한 마디로 하면 단례 모델을 선택한 것은 불일치 상태를 피하고 정출을 피하기 위해서다.
게으름뱅이와 굶주린 음식
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. 로드 지연/게으름뱅이
호출 방법 이후에 실례가 생성됩니다. 실현 방안은 무참구조 함수에 실례화를 해서 호출할 때만 대상의 실례, 코드를 만들 수 있습니다.

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
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());
결과
 1255301379
ReadResolve 메서드가 호출되었습니다!
 1255301379
동일한hashCode, 동일 대상 증명
5. 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
정적 코드 블록을 통해 한 번만 실행하는 특성도 성공적으로 라인이 안전한 단일 모드를 얻었다
6. 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();
  }
}

실행 결과
y Object 구조 호출
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());
    }
  }
}

결과
y Object 구조 호출
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
이상은 단리 모델과 다중 루틴을 결합할 때 만나는 각종 상황과 해결 방안을 총결하여 나중에 사용할 때 찾아볼 수 있도록 하였다.

좋은 웹페이지 즐겨찾기