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