001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.fukurou.util; 017 018import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 019import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 020import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 021import org.opengion.fukurou.system.DateSet; // 6.4.2.0 (2016/01/29) 022 023import java.util.MissingResourceException; 024import java.util.concurrent.ConcurrentMap; // 6.4.3.3 (2016/03/04) 025import java.util.concurrent.ConcurrentHashMap; // 6.4.3.1 (2016/02/12) refactoring 026import java.util.List; 027import java.util.ArrayList; 028import java.util.Iterator; 029import java.util.Collections; // 6.3.9.0 (2015/11/06) 030 031/** 032 * AbstractObjectPool は、生成された Object をプールするキャッシュクラスです。 033 * サブクラスで、各クラスごとにオブジェクトを生成/初期化/終了するように各メソッドを 034 * コーディングしなおしてください。 035 * サブクラスでは、Object createInstance() と、oid objectInitial( Object obj )、 036 * void objectFinal( Object obj ) を オーバーライドしてください。 037 * 038 * @version 4.0 039 * @author Kazuhiko Hasegawa 040 * @since JDK5.0, 041 */ 042public abstract class AbstractObjectPool<E> { 043 044 /** 内部でオブジェクトをプールしている配列。 */ 045 private List<E> pool ; // プールしているオブジェクト 046 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 047 private final ConcurrentMap<Integer,TimeStampObject> poolBkMap = new ConcurrentHashMap<>(); // 作成したオブジェクトのタイムスタンプ管理 048 private final Object lock = new Object(); // 6.3.9.0 (2015/11/06) ロック用のオブジェクト。poolとpoolBkMapを同時にロックする。 049 050 /** プール自体を拡張可能かどうかを決める変数。拡張制限(true)/無制限(false) */ 051 private boolean limit ; 052 053 /** 最大オブジェクト数 */ 054 private int maxsize ; 055 056 /** 生成したオブジェクトの寿命(秒)を指定します。 0 は、制限なしです。*/ 057 private int limitTime ; // 3.5.4.3 (2004/01/05) キャッシュの寿命を指定します。 058 059 /** 制限なしの場合でも、実質この値以上のキャッシュは、許可しません。*/ 060 private static final int MAX_LIMIT_COUNT = 1000 ; // 3.6.0.8 (2004/11/19) 061 062 /** 063 * デフォルトコンストラクター 064 * 065 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 066 */ 067 protected AbstractObjectPool() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 068 069 /** 070 * 初期化メソッド 071 * 072 * 初期オブジェクト数、最大オブジェクト数、拡張制限を指定します。 073 * 074 * 初期オブジェクト数は、プールを作成すると同時に確保するオブジェクトの個数です。 075 * オブジェクトの生成に時間がかかり、かつ、必ず複数使用するのであれば, 076 * 予め複数確保しておけば、パフォーマンスが向上します。 077 * 最大オブジェクト数は、拡張制限が、無制限(limit = false )の場合は、 078 * 無視されます。制限ありの場合は、この値を上限に、オブジェクトを増やします。 079 * 拡張制限は、生成するオブジェクト数に制限をかけるかどうかを指定します。 080 * 一般に、コネクション等のリソースを確保する場合は、拡張制限を加えて、 081 * 生成するオブジェクト数を制限します。 082 * 083 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 084 * 085 * @param minsize 初期オブジェクト数 086 * @param maxsize 最大オブジェクト数 087 * @param limit 拡張制限(true)/無制限(false) 088 */ 089 protected void init( final int minsize, final int maxsize, final boolean limit ) { 090 init( minsize, maxsize, limit,0 ) ; 091 } 092 093 /** 094 * 初期化メソッド 095 * 096 * 初期オブジェクト数、初期配列数、拡張制限、オブジェクトの寿命を指定します。 097 * 098 * 初期オブジェクト数、初期配列数、拡張制限、までは、{@link #init( int , int , boolean ) init} 099 * を参照してください。 100 * オブジェクトの寿命は、生成された時間からの経過時間(秒)だけ、キャッシュしておく 101 * 場合に使用します。 102 * 例えば、コネクション等で、長期間のプーリングがリソースを圧迫する場合や、 103 * 接続側自身が、タイマーで切断する場合など、オブジェクトの生存期間を 104 * 指定して管理する必要があります。 105 * 106 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 107 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 108 * 109 * @param minsize 初期オブジェクト数 110 * @param maxsize 初期配列数 111 * @param limit 拡張制限(true)/無制限(false) 112 * @param limitTime オブジェクトの寿命の時間制限値(秒) 113 * @see #init( int , int , boolean ) 114 */ 115 protected void init( final int minsize, final int maxsize,final boolean limit,final int limitTime ) { 116 this.maxsize = maxsize; 117 this.limit = limit; 118 this.limitTime = limitTime; 119 synchronized( lock ) { 120 pool = Collections.synchronizedList( new ArrayList<>( maxsize ) ); // 6.3.9.0 (2015/11/06) 121 poolBkMap.clear(); // 6.4.3.1 (2016/02/12) 122 for( int i=0; i<minsize; i++ ) { 123 final E obj = createInstance(); 124 pool.add( obj ); 125 126 final Integer key = Integer.valueOf( obj.hashCode() ); 127 poolBkMap.put( key,new TimeStampObject( obj,limitTime ) ); 128 } 129 } 130 } 131 132 /** 133 * キャッシュのインスタンスを返します。 134 * 135 * なお、拡張制限をしている場合に、最初に確保した数以上のオブジェクト生成の 136 * 要求があった場合は、 MissingResourceException が throw されます。 137 * また,オブジェクトが寿命を超えている場合は、削除した後、新たに次の 138 * オブジェクトの生成を行います。 139 * 140 * @og.rev 4.0.0.1 (2007/12/03) 生成リミットチェックを厳密に行う。 141 * @og.rev 4.0.0.1 (2007/12/03) 生成リミットエラー時に、タイムアウトをチェックする。 142 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 143 * 144 * @return キャッシュのインスタンス 145 * @throws MissingResourceException 拡張制限により、新しいインスタンスを生成できない場合 146 */ 147 public E newInstance() throws MissingResourceException { 148 final E rtnobj ; 149 synchronized( lock ) { 150 if( pool.isEmpty() ) { 151 if( limit && poolBkMap.size() >= maxsize ) { 152 final String errMsg = "生成リミットいっぱいで新たに生成できません。[" 153 + poolBkMap.size() + "]"; 154 155 // 4.0.0.1 (2007/12/03) 生成リミットエラー時に、タイムアウトをチェックする。 156 final Iterator<TimeStampObject> itr = poolBkMap.values().iterator(); 157 while( itr.hasNext() ) { 158 final TimeStampObject tso = itr.next(); 159 if( tso == null || tso.isTimeOver() ) { 160 itr.remove(); 161 } 162 } 163 164 throw new MissingResourceException( errMsg,getClass().getName(),"limit" ); 165 } 166 else if( poolBkMap.size() > MAX_LIMIT_COUNT ) { 167 clear(); // 全件キャッシュを破棄します。 168 final String errMsg = "ObjectPool で、メモリリークの可能性があります。size=[" 169 + poolBkMap.size() + "]"; 170 throw new OgRuntimeException( errMsg ); 171 } 172 // 新規作成 173 rtnobj = createInstance(); 174 final Integer key = Integer.valueOf( rtnobj.hashCode() ); 175 poolBkMap.put( key,new TimeStampObject( rtnobj,limitTime ) ); 176 } 177 else { 178 // 既存取り出し 179 rtnobj = pool.remove(0); 180 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 181 if( rtnobj == null ) { 182 // 通常ありえない。 183 final String errMsg = "オブジェクトの取得に失敗しました。" ; 184 throw new MissingResourceException( errMsg,getClass().getName(),"pool" ); 185 } 186 187 final Integer key = Integer.valueOf( rtnobj.hashCode() ); 188 final TimeStampObject tso = poolBkMap.get( key ); 189 if( tso == null || tso.isTimeOver() ) { 190 remove( rtnobj ); 191 return newInstance(); 192 } 193 } 194 } 195 return rtnobj; 196 } 197 198 /** 199 * 具体的に新しいインスタンスを生成するメソッド。 200 * 201 * サブクラスで具体的に記述する必要があります。 202 * 203 * @return 新しいインスタンス 204 */ 205 protected abstract E createInstance(); 206 207 /** 208 * オブジェクトを、オブジェクトプールに戻します。 209 * 戻すべきオブジェクトが null の場合は,削除されたと判断します。 210 * 211 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 212 * 213 * @param obj オブジェクトプールに戻すオブジェクト 214 */ 215 public void release( final E obj ) { 216 final E obj2 = objectInitial( obj ); 217 if( obj2 != null ) { 218 final Integer key = Integer.valueOf( obj2.hashCode() ); 219 synchronized( lock ) { 220 final TimeStampObject tso = poolBkMap.get( key ); 221 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 222 if( tso == null ) { 223 // 6.0.2.5 (2014/10/31) Ctrl-C で終了させると、なぜか poolBkMap から、オブジェクトが消える。原因不明???? 224 // LogWriter.log( "ObjectPool で、メモリリークの可能性がある。obj=[" + obj + "]" ); 225 remove( obj2 ); 226 } 227 else { 228 pool.add( obj2 ); 229 } 230 } 231 } 232 } 233 234 /** 235 * オブジェクトを、オブジェクトプールから削除します。 236 * remove されるオブジェクトは、すでにキャッシュから取り出された後なので、 237 * そのまま、何もしなければ自然消滅(GC)されます。 238 * 自然消滅する前に、objectFinal( Object ) が呼ばれます。 239 * 生成されたオブジェクトの総数も、ひとつ減らします。 240 * 241 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 242 * 243 * @param obj 削除するオブジェクト 244 */ 245 public void remove( final E obj ) { 246 if( obj != null ) { 247 final Integer key = Integer.valueOf( obj.hashCode() ); 248 synchronized( lock ) { 249 poolBkMap.remove( key ); 250 } 251 } 252 253 objectFinal( obj ); 254 } 255 256 /** 257 * オブジェクトプールの要素数を返します。 258 * 259 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 260 * 261 * @return プールの要素数 262 */ 263 public int size() { 264 synchronized( lock ) { 265 return poolBkMap.size(); 266 } 267 } 268 269 /** 270 * オブジェクトプールが要素を持たないかどうかを判定します。 271 * 272 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 273 * 274 * @return オブジェクトプールが要素を持っていない、つまりそのサイズが 0 の場合にだけ true、そうでない場合は false 275 */ 276 public boolean isEmpty() { 277 synchronized( lock ) { 278 return poolBkMap.isEmpty() ; 279 } 280 } 281 282 /** 283 * すべての要素を オブジェクトプールから削除します。 284 * 貸し出し中のオブジェクトは、クリアしません。よって、返り値は、 285 * すべてのオブジェクトをクリアできた場合は、true 、貸し出し中の 286 * オブジェクトが存在した場合(クリアできなかった場合)は、false です。 287 * 288 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 289 * 290 * @return すべてクリア(true)/貸し出し中のオブジェクトが残っている(false) 291 */ 292 public boolean clear() { 293 synchronized( lock ) { 294 final Iterator<E> itr = pool.iterator(); 295 while( itr.hasNext() ) { 296 remove( itr.next() ); 297 } 298 pool.clear(); 299 300 // 貸し出し中の場合は、remove 出来ない為、poolBkMap に残っている。 301 // それでも、poolBkMap をクリアすることで、release 返却時にも、 302 // remove されるようになります。 303 // ただし、作成オブジェクト数が、一旦 0 にリセットされる為、 304 // 最大貸し出し可能数が、一時的に増えてしまいます。 305 final boolean flag = poolBkMap.isEmpty(); 306 poolBkMap.clear(); 307 308 return flag; 309 } 310 } 311 312 /** 313 * オブジェクトプールから削除するときに呼ばれます。 314 * このメソッドで各オブジェクトごとの終了処理を行います。 315 * 例えば、データベースコネクションであれば、close() 処理などです。 316 * 317 * デフォルトでは、なにも行いません。 318 * 319 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 320 * 321 * @param obj 終了処理を行うオブジェクト 322 */ 323 protected void objectFinal( final E obj ) { 324 // ここでは処理を行いません。 325 } 326 327 /** 328 * オブジェクトプールに戻すとき(release するとき)に呼ばれます。 329 * このメソッドで各オブジェクトごとの初期処理を行います。 330 * オブジェクトプールに戻すときには、初期化して、次の貸し出しに 331 * 対応できるように、初期処理しておく必要があります。 332 * 333 * デフォルトでは、引数のオブジェクトをそのまま返します。 334 * 335 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 336 * 337 * @param obj 初期処理を行うオブジェクト 338 * 339 * @return 初期処理を行ったオブジェクト 340 */ 341 protected E objectInitial( final E obj ) { 342 return obj; 343 } 344 345 /** 346 * 内部状況を簡易的に表現した文字列を返します。 347 * 348 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 349 * 350 * @return このオブジェクトプールの文字列表現 351 * @og.rtnNotNull 352 */ 353 @Override 354 public String toString() { 355 synchronized( lock ) { 356 // 6.0.2.5 (2014/10/31) char を append する。 357 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 358 .append( " freeCount = [" ).append( pool.size() ).append( ']' ).append( CR ) 359 .append( " createCount = [" ).append( poolBkMap.size() ).append( ']' ) 360 .append( " ( max=[" ).append( maxsize ).append( "] )" ).append( CR ) 361 .append( " limiter = [" ).append( limit ).append( ']' ).append( CR ) 362 .append( " limitTime = [" ).append( limitTime ).append( "](s)" ).append( CR ); 363 364 final Iterator<E> itr = pool.iterator(); 365 buf.append( "Free Objects " ).append( CR ); 366 while( itr.hasNext() ) { 367 final E obj = itr.next(); 368 if( obj != null ) { 369 final Integer key = Integer.valueOf( obj.hashCode() ); 370 buf.append( ' ' ).append( poolBkMap.get( key ) ) 371 .append( ' ' ).append( obj ).append( CR ); 372 } 373 } 374 return buf.toString(); 375 } 376 } 377} 378 379/** 380 * TimeStampObject は、生成された Object を、生成時刻とともに管理するクラスです。 381 * 内部のハッシュキーは、登録するオブジェクトと同一で、管理できるのは、異なるオブジェクト 382 * のみです。 383 * 384 * @version 4.0 385 * @author Kazuhiko Hasegawa 386 * @since JDK5.0, 387 */ 388class TimeStampObject implements Comparable<TimeStampObject> { // 4.3.3.6 (2008/11/15) Generics警告対応 389 private final long timeStamp ; 390 private final long limitTime ; 391 private final String objStr ; // 6.0.2.5 (2014/10/31) 表示用 392 private final int hcode ; 393 394 /** 395 * コンストラクター。 396 * 397 * @param obj 管理するオブジェクト 398 * @param limit オブジェクトの寿命(秒) 399 * @throws IllegalArgumentException TimeStampObject のインスタンスに、NULL はセットできません。 400 */ 401 public TimeStampObject( final Object obj,final int limit ) { 402 if( obj == null ) { 403 final String errMsg = "TimeStampObject のインスタンスに、NULL はセットできません。" ; 404 throw new IllegalArgumentException( errMsg ); 405 } 406 407 timeStamp = System.currentTimeMillis(); 408 if( limit > 0 ) { 409 limitTime = timeStamp + limit * 1000L ; 410 } 411 else { 412 limitTime = Long.MAX_VALUE ; 413 } 414 415 hcode = (int)((timeStamp)&(Integer.MAX_VALUE))^(obj.hashCode()) ; 416 417 objStr = String.valueOf( obj ); // 6.0.2.5 (2014/10/31) 表示用 418 } 419 420 /** 421 * 内部管理しているオブジェクトの生成時刻を返します。 422 * 423 * @return 生成時刻(ms) 424 */ 425 public long getTimeStamp() { 426 return timeStamp; 427 } 428 429 /** 430 * オブジェクトの寿命がきたかどうかを返します。 431 * 432 * @return 寿命判定(true:寿命/false:まだ使える) 433 */ 434 public boolean isTimeOver() { 435 return System.currentTimeMillis() > limitTime ; 436 } 437 438 /** 439 * オブジェクトが同じかどうかを判定します。 440 * 441 * 内部オブジェクトの equals() メソッドと、作成時刻の両方を判断します。 442 * 内部オブジェクトの equals() が同じでも、作成時刻が異なると、 443 * false を返します。これは、全く同一オブジェクトを管理する場合でも、 444 * タイムスタンプを差し替える事で、異なるオブジェクトとして 445 * 認識させるということです。 446 * 447 * @param obj オブジェクト 448 * 449 * @return true:同じ/false:異なる。 450 */ 451 @Override 452 public boolean equals( final Object obj ) { 453 if( obj instanceof TimeStampObject ) { 454 final TimeStampObject other = (TimeStampObject)obj ; 455 return hcode == other.hcode && timeStamp == other.timeStamp ; 456 } 457 return false ; 458 } 459 460 /** 461 * ハッシュコードを返します。 462 * 463 * ここで返すのは、自分自身のハッシュコードではなく、 464 * 内部管理のオブジェクトのハッシュコードです。 465 * 466 * hashcode = (int)((timeStamp)&(Integer.MAX_VALUE))^(obj.hashCode()) 467 * 468 * この計算式は、変更される可能性があります。 469 * 470 * @return 内部管理のオブジェクトのハッシュコード 471 */ 472 @Override 473 public int hashCode() { return hcode; } 474 475 /** 476 * このオブジェクトと指定されたオブジェクトの順序を比較します。 477 * 478 * このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、 479 * 等しい場合はゼロ、大きい場合は正の整数を返します。 480 * 481 * @param other TimeStampObject オブジェクト 482 * 483 * @return 順序比較の値 484 * @throws ClassCastException 指定されたオブジェクトがキャストできない場合。 485 * @see Comparable#compareTo(Object) 486 */ 487 @Override // Comparable 488 public int compareTo( final TimeStampObject other ) { // 4.3.3.6 (2008/11/15) Generics警告対応 489 final long diff = timeStamp - other.timeStamp; 490 491 if( diff > 0 ) { return 1; } 492 else if( diff < 0 ) { return -1; } 493 else { 494 if( equals( other ) ) { return 0; } 495 else { return hcode - other.hcode; } 496 } 497 } 498 499 /** 500 * このオブジェクトの内部表現を返します。 501 * 502 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。 503 * 504 * @return オブジェクトの内部表現文字列 505 * @og.rtnNotNull 506 */ 507 @Override 508 public String toString() { 509 // Create Timeは、一度求めれば変わらないので、キャッシュしても良い。 510 return "[CreateTime=" + DateSet.getDate( timeStamp,"yyyy/MM/dd HH:mm:ss" ) 511 + " , TimeOver=" + (int)((limitTime - System.currentTimeMillis())/1000.0) + "(s)" 512 + " , object=" + objStr + "]" ; // 6.0.2.5 (2014/10/31) 表示用 513 } 514}