【構造型モード12】享元モード-2(Flyweight)

12654 ワード

3.3メタオブジェクトの管理
共有されたメタオブジェクトインスタンスの管理要件は、インスタンスプールのインスタンス管理要件ほど高くありませんが、参照カウント、ゴミ除去など、独自の特徴的な機能もたくさんあります.ゴミとは、キャッシュに存在するが、使用されるキャッシュ内のオブジェクトは不要である.
引用カウントとは、享元工場が各享元が使用された回数を記録できることである.ごみの除去は、ほとんどのキャッシュ管理にある機能で、キャッシュは中にデータを入れるだけではなく、これらのデータが必要ないときは、これらのデータをキャッシュから除去し、対応するメモリ空間を解放して、資源を節約しなければならない.
前の例では、共有されたメタオブジェクトは多くの人が共有しており、消去することなく、基本的にシステムに常に存在することができる.しかし、ゴミ除去はメタオブジェクト管理の一般的な機能であるか、例を挙げて、これらの一般的な機能をどのように実現するかを示します.
  • 参照カウントを実現するための基本構想
  • 参照カウントを実現するには、享元ファクトリでMapを定義します.そのkey値は、享元オブジェクトをキャッシュするkeyと同じですが、valueは参照された回数です.これにより、外部がその享元を取得するたびに、対応する参照カウントを1加算して記録します.
  • ごみ回収を実現するための基本構想
  • ごみの回収を実現するには面倒な点がありますが、まずどれがごみなのかを確定することができますか?次はいつ回収しますか?誰が回収しますか?回収方法これらの問題を解決すれば、ごみの回収も実現できます.
    どのゴミがゴミであるかを決定するために、簡単な方法は、キャッシュオブジェクトの構成オブジェクトを定義し、このオブジェクトにキャッシュの開始時間と最長使用されない時間が記述されている.このとき、ゴミの計算式は、現在の時間-キャッシュの開始時間>=最長使用されない時間であると判断する.もちろん,このオブジェクトが使用されるたびに,そのキャッシュ開始時刻を使用時の現在時刻に更新する,すなわち,常に誰かが使用していれば,このオブジェクトはゴミと判断されない.
    いつ回収するかという問題は、もちろんゴミだと判断すれば回収できます.
    肝心なのは誰がゴミを判断し、誰がゴミを回収するかという問題です.簡単なスキームは、享元工場が作成されたときに実行を開始する内部スレッドを定義することです.このスレッドでは、キャッシュ内のすべてのオブジェクトのキャッシュ構成を一定時間おきにループし、ゴミかどうかを確認し、ゴミであればリサイクルを開始できます.
    どのように回収しますか?これは比較的簡単で、キャッシュされたmapオブジェクトから対応するオブジェクトを直接削除し、参照する場所がないようにすれば、仮想マシンのゴミ回収で回収されるのを待つことができます.
  • コード例
  • (1)これだけ分析したのか、コードの例を見たほうが明らかで、まずキャッシュ構成オブジェクトを見て、サンプルコードは以下の通りです.
    /**
       *              
       */
    public class CacheConfModel{
          /**
           *            
           */
          private long beginTime;
          /**
           *            ,            
           */
          private double durableTime;
          /**
           *            ,            
           */
          private boolean forever;
          public boolean isForever() {
             return forever;
          }
          public void setForever(boolean forever) {
             this.forever = forever;
          }
          public long getBeginTime() {
             return beginTime;
          }
          public void setBeginTime(long beginTime) {
             this.beginTime = beginTime;
          }
          public double getDurableTime() {
             return durableTime;
          }
          public void setDurableTime(double durableTime) {
             this.durableTime = durableTime;
          }
    }
    

    (2)享元対象の管理は、享元工場によって行われるため、上記の機能は、享元工場にも集中して実現されており、前の例の上で、これらの機能を実現するために、改善された享元工場は相対的にやや複雑であり、大きく以下のような変化がある.
    共有オブジェクトのキャッシュ構成のデータをキャッシュするMapを追加します.
    キャッシュされたオブジェクトが参照された回数を記録するMapを追加します.
    テストを容易にするために、キャッシュの持続時間を記述する定数を定義した.
    あるエンティティが使用される回数を取得する方法を提供する.
    享元を取得するオブジェクトでは、対応する参照カウントとキャッシュ設定を設定します.例は内部デフォルトでキャッシュ設定を設定しますが、実際には享元を取得する方法を改造して、外部からキャッシュ設定のデータを転送することもできます.
    キャッシュをクリアするスレッドを提供し、キャッシュデータがゴミであるかどうかを判断し、もしそうであれば、キャッシュからクリアする.
    基本的には享元工場が再実現され、サンプルコードは以下の通りです.
    /**
       *     ,        
       *                 
       */
    public class FlyweightFactory {
          private static FlyweightFactory factory = new FlyweightFactory();
          private FlyweightFactory(){
             //          
             Thread t = new ClearCache();
             t.start();
          }
          public static FlyweightFactory getInstance(){
             return factory;
          }
    
          /**
           *     flyweight  
           */
          private Map fsMap = new HashMap();
          /**
           *               ,key    map   
           */
          private Map cacheConfMap = new HashMap();
          /**
           *               ,key    map   
           */
          private Map countMap = new HashMap();
          /**
           *     6  ,        ,               
           */
          private final long DURABLE_TIME = 6*1000L;
      
          /**
           *             
           * @param key    key
           * @return       
           */
          public synchronized int getUseTimes(String key){
             Integer count = countMap.get(key);
             if(count==null){
                 count = 0;
             }
             return count;
          }
          /**
           *   key       
           * @param key        key
           * @return key       
           */
          public synchronized Flyweight getFlyweight(String key) {
             Flyweight f = fsMap.get(key);
             if(f==null){
                 f = new AuthorizationFlyweight(key);
                 fsMap.put(key,f);
                 //        
                 countMap.put(key, 1);
    
                 //          
                 CacheConfModel cm = new CacheConfModel();
                 cm.setBeginTime(System.currentTimeMillis());
                 cm.setForever(false);
                 cm.setDurableTime(DURABLE_TIME);
             
                 cacheConfMap.put(key, cm);
             }else{
                 //      ,            
                 CacheConfModel cm = cacheConfMap.get(key);
                 cm.setBeginTime(System.currentTimeMillis());
                 //    
                 this.cacheConfMap.put(key, cm);
                 //     1
                 Integer count = countMap.get(key);
                 count++;
                 countMap.put(key, count);
             }
             return f;
          }
          /**
           *   key       ,                   ,   
           * @param key          key
           */
          private synchronized void removeFlyweight(String key){
             this.fsMap.remove(key);
             this.cacheConfMap.remove(key);
             this.countMap.remove(key);
          }
          /**
           *          ,    
           */
          private  class ClearCache extends Thread{
             public void run(){
                 while(true){
                    Set tempSet = new HashSet();
                    Set set = cacheConfMap.keySet();
                    for(String key : set){
                        CacheConfModel ccm = cacheConfMap.get(key);
                        //        
                        if((System.currentTimeMillis() - ccm.getBeginTime()) >= ccm.getDurableTime()){
                           //    ,     
                           tempSet.add(key);
                        }
                    }
                    //    
                    for(String key : tempSet){
                        FlyweightFactory.getInstance().removeFlyweight(key);
                    }
                    System.out.println("now thread="+fsMap.size() +",fsMap=="+fsMap.keySet());
                    //  1      
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                 }
             }
          }
    }
    

    注意:getUseTimes、removeFlyweight、getFlyweightのいくつかの方法は同期化されています.これは、マルチスレッド環境で使用すると、1つのスレッドがメタオブジェクトを取得し、もう1つのスレッドがこのキャッシュオブジェクトを削除するなど、同時エラーが発生しやすいためです.
    (3)参照カウントの効果を見るには,SecurityMgrを少し修正する必要があるが,少なくともデータをキャッシュしないで,享元工場から直接データを取得しなければならない.そうしないと,正確にカウントを参照することはできない.
    ログイン者対応権限データを配置するキャッシュが削除されました.
    ログイン機能を実現する必要はありません.このプレゼンテーションプログラムでは、ログイン方法はすでに機能を実現する必要はありません.そのため、直接削除します.
    もともとmapで値を取得していたところを、queryByUserで直接取得すればいいのです.
    サンプルコードは次のとおりです.
    public class SecurityMgr {
          private static SecurityMgr securityMgr = new SecurityMgr();
          private SecurityMgr(){     
          }
          public static SecurityMgr getInstance(){
             return securityMgr;
          }
          /**
           *                    
           * @param user         
           * @param securityEntity     
           * @param permit   
           * @return true        ,false        
           */
          public boolean hasPermit(String user,String securityEntity,String permit){
             Collection col = this.queryByUser(user);
             if(col==null || col.size()==0){
                 System.out.println(user+"               ");
                 return false;
             }
             for(Flyweight fm : col){
                 if(fm.match(securityEntity, permit)){
                    return true;
                 }
             }
             return false;
          }
          /**
           *                
           * @param user              
           * @return         
           */
          private Collection queryByUser(String user){
             Collection col = new ArrayList();
         
             for(String s : TestDB.colDB){
                 String ss[] = s.split(",");
                 if(ss[0].equals(user)){
                    Flyweight fm = null;
                    if(ss[3].equals("2")){
                        //     
                        fm = new UnsharedConcreteFlyweight();
                        //         
                        String tempSs[] = TestDB.mapDB.get(ss[1]);
                        for(String tempS : tempSs){
                           Flyweight tempFm = FlyweightFactory.getInstance().getFlyweight(tempS);
                           //             
                           fm.add(tempFm);
                        }
                    }else{
                        fm = FlyweightFactory.getInstance().getFlyweight(ss[1]+","+ss[2]);
                    }            
                    col.add(fm);
                 }
             }
             return col;
          }  
    }
    

    (4)クライアントを書いてみるか、上の享元工場が享元オブジェクトの管理を実現できるかどうか、特にゴミ回収とカウントの機能については、ゴミ回収の機能にテストコードを追加する必要はありませんが、カウントを参照する機能については、コードを書いて呼び出す必要があります.サンプルコードは以下の通りです.
    public class Client {
          public static void main(String[] args) throws Exception{
             SecurityMgr mgr = SecurityMgr.getInstance();
             boolean f1 = mgr.hasPermit("  ","    ","  ");
             boolean f2 = mgr.hasPermit("  ","    ","  ");
             boolean f3 = mgr.hasPermit("  ","    ","  ");
    
             for(int i=0;i<3;i++){
                 mgr.hasPermit("  "+i,"    ","  ");
             }  
         
             //    :         ,          ,   
             //SecurityMgr queryByUser                  
             System.out.println("    ,       "+FlyweightFactory.getInstance().getUseTimes("    ,  ")+" ");
             System.out.println("    ,       "+FlyweightFactory.getInstance().getUseTimes("    ,  ")+" ");
             System.out.println("    ,       "+FlyweightFactory.getInstance().getUseTimes("    ,  ")+" ");
          }
    }
    

    キャッシュされたゴミ回収機能はスレッドが実行されているので、スレッドの実行を終了しないで、プログラムはずっと実行され、実行の結果は以下の通りです.
        ,       2 
        ,       2 
        ,       6 
    now thread=3,fsMap==[    ,  ,     ,  ,     ,  ]
    now thread=3,fsMap==[    ,  ,     ,  ,     ,  ]
    now thread=3,fsMap==[    ,  ,     ,  ,     ,  ]
    now thread=3,fsMap==[    ,  ,     ,  ,     ,  ]
    now thread=3,fsMap==[    ,  ,     ,  ,     ,  ]
    now thread=3,fsMap==[    ,  ,     ,  ,     ,  ]
    now thread=0,fsMap==[]
    now thread=0,fsMap==[]
    

    3.4享元モードの長所と短所##
  • オブジェクト数を減らし、メモリ容量を節約
  • 共有オブジェクトはスペースを無駄にすると思っている友人もいるかもしれませんが、これらのオブジェクトが頻繁に使用されると、実はスペースを節約します.占有領域のサイズは、各オブジェクトインスタンスが占有するサイズに等しいため、享元オブジェクトでは基本的に1つのインスタンスしかなく、享元オブジェクトの数を大幅に削減し、メモリ領域を大幅に節約します.
    節約されるスペースは、共有によって減少するインスタンスの数、各インスタンス自体が占有するスペースに依存します.各オブジェクトインスタンスが2バイトを占有する場合、共有されない数が100個であり、共有された後に1つしかない場合、節約されたスペースは、(100−1)X 2バイトに等しい.
  • 共有オブジェクトを維持するには、追加のオーバーヘッド
  • が必要です.
    前述した享元工場のように、共有オブジェクトを維持する際に、機能が複雑である場合、ゴミ回収を維持するためのスレッドなどの追加のオーバーヘッドが発生します.
    3.5思考享元モード##
  • 享元モードの本質
  • メタモードの本質:分離と共有.
    分離するのは、オブジェクトの状態で変化する部分と変化しない部分であり、共有するのはオブジェクトの変化しない部分である.享元モードのポイントは、変化と不変を分離し、不変の部分を享元オブジェクトの内部状態とし、変化の部分を外部状態とし、外部によって維持することで、享元オブジェクトを共有することができ、オブジェクトの数を減らし、メモリスペースを大幅に節約することができます.
    この本質を理解すると,享元モードを用いる場合,どのような状態が分離する必要があるかを考える.どのように分離しますか?分離後はどうしますか?共有する必要があるものは何ですか?共有オブジェクトの管理方法外部で共有されたメタオブジェクトをどのように使用しますか?共有しないオブジェクトは必要ですか?などの問題があります.
    これらの問題をすべてはっきり考えて、相応の解決方法を見つけて、それでは享元モードも応用して、標準的な応用かもしれないし、変形の応用かもしれないが、万変はその宗から離れない.
  • ユーティリティーモード
  • がいつ選択されるか
    次の場合は、ユーティリティーモードを選択することをお勧めします.
    アプリケーションで多数のパーティクルオブジェクトが使用されている場合は、メタモードを使用してオブジェクトの数を減らすことができます.
    大量のオブジェクトを使用するため、ストレージのオーバーヘッドが大きい場合は、メタモードを使用してオブジェクトの数を減らし、メモリを節約できます.
    オブジェクトのほとんどの状態が外部状態に遷移できる場合、例えば計算によって得られるか、外部から伝達されるかなど、享元モードを使用して内部状態と外部状態の分離を実現することができる.
    オブジェクトの外部状態を考慮しない場合、多くの組合せオブジェクトを比較的少ない共有オブジェクトで置き換えることができ、共有メタモードを使用してオブジェクトを共有し、オブジェクトを組み合わせてこれらの共有オブジェクトを使用することができます.
    3.6相関モード##
  • 享元モードと一例モード
  • この2つのモードを組み合わせて使用できます.
    通常、享元モードにおける享元工場は、一例として実現することができる.また,享元ファクトリにキャッシュされた享元オブジェクトは,いずれも単一例であり,単一例モードの変形制御と見なされ,享元ファクトリに単一例享元オブジェクトが来る.
  • 享元モードと組合せモード
  • この2つのモードを組み合わせて使用できます.
    享元モードでは、共有を必要としない享元実装が存在し、これらの共有を必要としない享元は通常、共有された享元オブジェクトの組合せオブジェクトであり、すなわち、享元モードは通常、組合せモードと組み合わせて使用され、より複雑なオブジェクト階層を実現する.
  • エンティティモードとステータスモード
  • この2つのモードを組み合わせて使用できます.
    享元モードを使用して状態モードの状態オブジェクトを共有することができます.通常、状態モードでは、多数の細粒度の状態オブジェクトが存在し、基本的には繰り返し使用することができます.固定された状態を処理するために使用されます.必要なデータは通常、コンテキストによって伝達され、つまり変化部分が分離されます.したがって、これらのステータスオブジェクトは、メタモードで実現することができる.
  • エンティティモードとポリシーモード
  • この2つのモードを組み合わせて使用できます.
    共有モードを使用してポリシーモードのポリシーオブジェクトを実装できます.ステータスモードと同様に、ポリシーモードにも微細度のポリシーオブジェクトが多数存在し、必要なデータはコンテキストから伝達されるため、共有モードを使用してこれらのポリシーオブジェクトを実装できます.