オタクタイム-デザインパターンの美しさシングルトンモード(パート2):クラスター環境で分散シングルトンモードを設計および実装する方法は?

シングルトンモードを目指して、シングルトンのアプリケーションシナリオ、いくつかの一般的なコードの実装、および既存の問題について説明し、ファクトリモードやIOCコンテナなどのシングルトンモードを置き換える方法の大まかなアイデアを示しました。今日は、それをさらに拡張して、次の問題について一緒に話し合いましょう。

●シングルトンパターンの独自性をどのように理解しますか?

●スレッドのシングルトンを実現するにはどうすればよいですか?

●クラスター環境でシングルトンを実装するにはどうすればよいですか?

●マルチケースパターンを実装するにはどうすればよいですか?

今日のコンテンツは少し「頭がおかしい」ので、読んでいくうちにもっと考えてほしいと思います。言うまでもありませんが、今日の学習を正式に始めましょう!

シングルトンパターンの独自性を理解するにはどうすればよいですか?

まず、シングルトンの定義をもう一度見てみましょう。「クラスは1つのオブジェクト(またはインスタンス)のみを作成できます。このクラスはシングルトンクラスです。このデザインパターンは、シングルトンデザインパターンまたは略してシングルトンパターンと呼ばれます。 。」

定義には、「クラスは1つのオブジェクトのみを作成することが許可されている」と記載されています。オブジェクトの一意性の範囲は何ですか?スレッドで作成できるオブジェクトが1つだけであることを意味しますか、それともプロセスで作成できるオブジェクトが1つだけであることを意味しますか?答えは後者です。つまり、シングルトンパターンによって作成されたオブジェクトはプロセスに固有です。ここで理解するのは少し難しいですが、詳しく説明させてください。

私たちが作成、コンパイル、リンク、および整理したコードは、オペレーティングシステムが実行できるファイルを構成します。これは、通常「実行可能ファイル」と呼ばれるものです(Windowsのexeファイルなど)。実行可能ファイルは、実際には、コードが理解可能なオペレーティングシステムに変換される一連の命令であり、コード自体として簡単に理解できます。

コマンドラインを使用するか、ダブルクリックして実行可能ファイルを実行すると、オペレーティングシステムはプロセスを開始し、実行可能ファイルをディスクから独自のプロセスアドレススペースにロードします(オペレーティングシステムによってプロセスに割り当てられたメモリストレージ領域を理解できます。コードとデータを保存します)。次に、プロセスは実行可能ファイルに含まれているコードを1つずつ実行します。たとえば、プロセスがコード内のUser user = new User();ステートメントを読み取ると、アドレス空間にユーザー一時変数とユーザーオブジェクトが作成されます。

アドレススペースはプロセス間で共有されません。1つのプロセスで別のプロセスを作成する場合(たとえば、コードにfork()ステートメントがある場合、プロセスがこのステートメントを実行すると、新しいプロセスが作成されます)、オペレーティングシステム新しいアドレススペースが新しいプロセスに割り当てられ、古いプロセスのアドレススペースのすべてのコンテンツが新しいプロセスのアドレススペースにコピーされます。これらのコンテンツには、コードとデータ(ユーザー一時変数、ユーザーオブジェクトなど)が含まれます。

したがって、シングルトンクラスは古いプロセスに存在し、1つのオブジェクトのみが存在できます。また、新しいプロセスにも存在し、1つのオブジェクトのみが存在できます。さらに、これら2つのオブジェクトは同じオブジェクトではありません。つまり、シングルトンクラスのオブジェクトの一意性の範囲はプロセス内にあり、プロセス間で一意ではありません。

唯一のシングルトンスレッドを実装するにはどうすればよいですか?

シングルトンオブジェクトはプロセスに固有であり、プロセスは1つのシングルトンオブジェクトしか持つことができないと述べました。では、スレッドに固有のシングルトンを実装するにはどうすればよいでしょうか。

まず、スレッドの唯一のシングルトンとは何か、および「スレッドのみ」と「プロセスのみ」の違いを見てみましょう。

「プロセス固有」とは、プロセス間で一意ではなく、プロセス内で一意であることを指します。類推により、「スレッドの一意性」はスレッド内の一意性を指し、スレッドは一意でない場合があります。実際、「プロセス固有」とは、スレッド内およびスレッド間で固有であることも意味します。これは、「プロセス固有」と「スレッド固有」の違いでもあります。この一節は少し舌ねじれのように聞こえますが、例を挙げて説明しましょう。

IdGeneratorがスレッドの唯一のシングルトンクラスであると想定します。スレッドAでは、シングルトンオブジェクトaを作成できます。スレッドは一意であるため、スレッドAで新しいIdGeneratorオブジェクトを作成することはできず、スレッドを一意でなくすることもできます。したがって、別のスレッドBで、新しいシングルトンオブジェクトbを再作成することもできます。

概念は理解するのが複雑ですが、以下に示すように、スレッドのみのシングルトンのコード実装は非常に単純です。コードでは、HashMapを使用してオブジェクトを格納します。ここで、keyはスレッドID、valueはオブジェクトです。このようにして、異なるスレッドが異なるオブジェクトに対応し、同じスレッドが1つのオブジェクトにのみ対応できるようにすることができます。実際、Java言語自体がThreadLocalツールクラスを提供しているため、スレッド固有のシングルトンを簡単に実装できます。ただし、ThreadLocalの基本的な実装原則も、次のコードに示すHashMapに基づいています。


public class IdGenerator {
    
    
  private AtomicLong id = new AtomicLong(0);

  private static final ConcurrentHashMap<Long, IdGenerator> instances
          = new ConcurrentHashMap<>();

  private IdGenerator() {
    
    }

  public static IdGenerator getInstance() {
    
    
    Long currentThreadId = Thread.currentThread().getId();
    instances.putIfAbsent(currentThreadId, new IdGenerator());
    return instances.get(currentThreadId);
  }

  public long getId() {
    
    
    return id.incrementAndGet();
  }
}

クラスター環境でシングルトンを実装するにはどうすればよいですか?

「プロセスのみ」のシングルトンと「スレッドのみ」のシングルトンについて説明しましたが、次に「クラスターのみ」のシングルトンを見てみましょう。

まず、「クラスターのみ」のシングルトンとは何かを説明しましょう。

「プロセスのみ」と「スレッドのみ」と比較してみましょう。「プロセスの一意性」とは、プロセス間の一意性ではなく、プロセス内の一意性を指します。「スレッドの一意性」とは、スレッド内で一意であり、スレッド間で一意ではないことを指します。クラスターは、複数のプロセスのコレクションに相当します。「クラスター固有」は、プロセス内で一意であり、プロセス間で一意であることに相当します。つまり、異なるプロセスが同じオブジェクトを共有し、同じクラスの複数のオブジェクトを作成することはできません。

従来のシングルトンモデルはプロセス内で一意であることがわかっているので、プロセス間でも一意であるシングルトンを実現するにはどうすればよいでしょうか。異なるプロセス間での同じオブジェクトの共有に厳密に従って実装されている場合、クラスター内に唯一のシングルトンを実装することは少し困難です。

具体的には、このシングルトンオブジェクトをシリアル化し、外部の共有ストレージ領域(ファイルなど)に保存する必要があります。プロセスがこのシングルトンオブジェクトを使用する場合、外部共有ストレージ領域からメモリに読み取り、オブジェクトに逆シリアル化してから使用する必要があります。使用が完了したら、外部共有ストレージ領域に保存する必要があります。

プロセス間にオブジェクトのコピーが常に1つだけ存在するようにするには、プロセスがオブジェクトを取得した後、他のプロセスがオブジェクトを再度取得できないようにオブジェクトをロックする必要があります。プロセスがオブジェクトの使用を終了した後、メモリからオブジェクトを明示的に削除し、オブジェクトのロックを解除する必要があります。

この考えによれば、次のように、疑似コードを使用してこのプロセスを実装しました。


public class IdGenerator {
    
    
  private AtomicLong id = new AtomicLong(0);
  private static IdGenerator instance;
  private static SharedObjectStorage storage = FileSharedObjectStorage(/*入参省略,比如文件地址*/);
  private static DistributedLock lock = new DistributedLock();
  
  private IdGenerator() {
    
    }

  public synchronized static IdGenerator getInstance() 
    if (instance == null) {
    
    
      lock.lock();
      instance = storage.load(IdGenerator.class);
    }
    return instance;
  }
  
  public synchroinzed void freeInstance() {
    
    
    storage.save(this, IdGeneator.class);
    instance = null; //释放对象
    lock.unlock();
  }
  
  public long getId() {
    
     
    return id.incrementAndGet();
  }
}

// IdGenerator使用举例
IdGenerator idGeneator = IdGenerator.getInstance();
long id = idGenerator.getId();
IdGenerator.freeInstance();

マルチケースパターンを実装する方法は?

シングルトンパターンの概念に対応するマルチケースパターンもあります。マルチケースパターンを実装する方法は?

「シングルトン」とは、クラスが1つのオブジェクトしか作成できないことを意味します。これに対応して、「複数のインスタンス」とは、クラスが複数のオブジェクトを作成できることを意味しますが、作成できるオブジェクトは3つだけであるなど、数に制限があります。コードを使用して簡単な例を示すと、次のようになります。


public class BackendServer {
    
    
  private long serverNo;
  private String serverAddress;

  private static final int SERVER_COUNT = 3;
  private static final Map<Long, BackendServer> serverInstances = new HashMap<>();

  static {
    
    
    serverInstances.put(1L, new BackendServer(1L, "192.134.22.138:8080"));
    serverInstances.put(2L, new BackendServer(2L, "192.134.22.139:8080"));
    serverInstances.put(3L, new BackendServer(3L, "192.134.22.140:8080"));
  }

  private BackendServer(long serverNo, String serverAddress) {
    
    
    this.serverNo = serverNo;
    this.serverAddress = serverAddress;
  }

  public BackendServer getInstance(long serverNo) {
    
    
    return serverInstances.get(serverNo);
  }

  public BackendServer getRandomInstance() {
    
    
    Random r = new Random();
    int no = r.nextInt(SERVER_COUNT)+1;
    return serverInstances.get(no);
  }
}

実際、マルチケースパターンを理解する別の方法があります。同じタイプのオブジェクトを1つだけ作成でき、異なるタイプの複数のオブジェクトを作成できます。ここで「タイプ」を理解する方法は?

例を挙げて説明しましょう。具体的なコードは次のとおりです。コードでは、ロガー名は上記の「タイプ」です。同じロガー名で取得されたオブジェクトインスタンスは同じであり、異なるロガー名で取得されたオブジェクトインスタンスは異なります。


public class Logger {
    
    
  private static final ConcurrentHashMap<String, Logger> instances
          = new ConcurrentHashMap<>();

  private Logger() {
    
    }

  public static Logger getInstance(String loggerName) {
    
    
    instances.putIfAbsent(loggerName, new Logger());
    return instances.get(loggerName);
  }

  public void log() {
    
    
    //...
  }
}

//l1==l2, l1!=l3
Logger l1 = Logger.getInstance("User.class");
Logger l2 = Logger.getInstance("User.class");
Logger l3 = Logger.getInstance("Order.class");

このマルチケースパターンを理解する方法は、工場出荷時のパターンと多少似ています。それとファクトリパターンの違いは、マルチケースパターンによって作成されたオブジェクトはすべて同じクラスのオブジェクトであるのに対し、ファクトリパターンは異なるサブクラスのオブジェクトを作成することです。この点については次のレッスンで説明します。実際、これはFlyweightモデルにいくぶん似ており、Flyweightモデルについて説明するときに2つの違いを分析します。さらに、実際には、列挙型はマルチケースモードと同等であり、1つの型は1つのオブジェクトにのみ対応でき、1つのクラスは複数のオブジェクトを作成できます。

おすすめ

転載: blog.csdn.net/zhujiangtaotaise/article/details/110443278