Javaにおけるオブジェクトプールパターン:再利用可能なオブジェクト管理によるパフォーマンス向上
別名
- リソースプール
オブジェクトプールデザインパターンの意図
Javaのオブジェクトプールデザインパターンは、再利用可能なオブジェクトのプールを管理し、オブジェクトを繰り返し作成および破棄するのではなく、再利用することでメモリ管理とアプリケーションのパフォーマンスを最適化します。
オブジェクトプールパターンの詳細な解説と実世界での例
実世界の例
需要の高い、限られた数の自習室がある図書館を想像してください。学生が自習室が必要になるたびに自習室を建てるのではなく、図書館は利用可能な自習室のプールを管理します。学生が自習室を必要とするとき、彼らはプールから1つチェックアウトします。彼らが終わった後、彼らは他の人が使用できるように部屋をプールに戻します。これにより、自習室は毎回新しい部屋を建てる必要なく効率的に利用され、時間とリソースを節約できます。これは、オブジェクトプールパターンがソフトウェアで高価なオブジェクトの再利用を管理する方法と似ています。
平易な言葉で
オブジェクトプールは、オンデマンドでインスタンスを作成および破棄する代わりに、インスタンスのセットを管理します。
Wikipediaによると
オブジェクトプールパターンは、オンデマンドでオブジェクトを割り当てて破棄するのではなく、使用する準備が整った初期化されたオブジェクトのセット(「プール」)を使用するソフトウェアの生成デザインパターンです。
Javaでのオブジェクトプールパターンのプログラミング例
私たちの戦争ゲームでは、巨大で神話的な獣であるオリファントを使用する必要がありますが、問題はそれらの作成が非常に高価であることです。解決策は、それらのプールを作成し、どれが使用中であるかを追跡し、それらを破棄する代わりにインスタンスを再利用することです。
これが基本的なOliphaunt
クラスです。これらの巨人は、作成するのに非常に費用がかかります。
public class Oliphaunt {
private static final AtomicInteger counter = new AtomicInteger(0);
@Getter
private final int id;
public Oliphaunt() {
id = counter.incrementAndGet();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LOGGER.error("Error occurred: ", e);
}
}
@Override
public String toString() {
return String.format("Oliphaunt id=%d", id);
}
}
次に、ObjectPool
、より具体的にはOliphauntPool
を示します。
public abstract class ObjectPool<T> {
private final Set<T> available = new HashSet<>();
private final Set<T> inUse = new HashSet<>();
protected abstract T create();
public synchronized T checkOut() {
if (available.isEmpty()) {
available.add(create());
}
var instance = available.iterator().next();
available.remove(instance);
inUse.add(instance);
return instance;
}
public synchronized void checkIn(T instance) {
inUse.remove(instance);
available.add(instance);
}
@Override
public synchronized String toString() {
return String.format("Pool available=%d inUse=%d", available.size(), inUse.size());
}
}
public class OliphauntPool extends ObjectPool<Oliphaunt> {
@Override
protected Oliphaunt create() {
return new Oliphaunt();
}
}
最後に、プールの使用方法を示します。
public static void main(String[] args) {
var pool = new OliphauntPool();
LOGGER.info(pool.toString());
var oliphaunt1 = pool.checkOut();
String checkedOut = "Checked out {}";
LOGGER.info(checkedOut, oliphaunt1);
LOGGER.info(pool.toString());
var oliphaunt2 = pool.checkOut();
LOGGER.info(checkedOut, oliphaunt2);
var oliphaunt3 = pool.checkOut();
LOGGER.info(checkedOut, oliphaunt3);
LOGGER.info(pool.toString());
LOGGER.info("Checking in {}", oliphaunt1);
pool.checkIn(oliphaunt1);
LOGGER.info("Checking in {}", oliphaunt2);
pool.checkIn(oliphaunt2);
LOGGER.info(pool.toString());
var oliphaunt4 = pool.checkOut();
LOGGER.info(checkedOut, oliphaunt4);
var oliphaunt5 = pool.checkOut();
LOGGER.info(checkedOut, oliphaunt5);
LOGGER.info(pool.toString());
}
プログラム出力
21:21:55.126 [main] INFO com.iluwatar.object.pool.App -- Pool available=0 inUse=0
21:21:56.130 [main] INFO com.iluwatar.object.pool.App -- Checked out Oliphaunt id=1
21:21:56.132 [main] INFO com.iluwatar.object.pool.App -- Pool available=0 inUse=1
21:21:57.137 [main] INFO com.iluwatar.object.pool.App -- Checked out Oliphaunt id=2
21:21:58.143 [main] INFO com.iluwatar.object.pool.App -- Checked out Oliphaunt id=3
21:21:58.145 [main] INFO com.iluwatar.object.pool.App -- Pool available=0 inUse=3
21:21:58.145 [main] INFO com.iluwatar.object.pool.App -- Checking in Oliphaunt id=1
21:21:58.145 [main] INFO com.iluwatar.object.pool.App -- Checking in Oliphaunt id=2
21:21:58.146 [main] INFO com.iluwatar.object.pool.App -- Pool available=2 inUse=1
21:21:58.146 [main] INFO com.iluwatar.object.pool.App -- Checked out Oliphaunt id=2
21:21:58.146 [main] INFO com.iluwatar.object.pool.App -- Checked out Oliphaunt id=1
21:21:58.147 [main] INFO com.iluwatar.object.pool.App -- Pool available=0 inUse=3
Javaでオブジェクトプールパターンを使用するタイミング
次のような場合にオブジェクトプールパターンを使用します
- オブジェクトを頻繁に作成および破棄する必要があり、リソースの割り当てと解放のコストが高くなる場合。
- オブジェクトの作成と維持にコストがかかる場合(例:データベース接続、スレッドプール)。
- 接続プーリングのように、固定数のオブジェクトを制御する必要がある場合。
- オブジェクトの再利用が、システムのパフォーマンスとリソース管理を大幅に向上させることができる場合。
Javaにおけるオブジェクトマザーパターンの実世界での応用
- Javaアプリケーションでのデータベース接続プーリング。
- Java並行プログラミングでのスレッドプーリング。
- ネットワークアプリケーションでのソケット接続のプーリング。
- 頻繁に作成および破棄されるゲームオブジェクトのゲーム開発におけるオブジェクトプール。
オブジェクトプールパターンの利点とトレードオフ
利点
- パフォーマンスの向上:オブジェクトの作成とガベージコレクションのオーバーヘッドを削減します。
- リソース管理:インスタンスの数を制御し、リソースの競合を減らし、リソースの使用量を制限します。
- スケーラビリティ:固定数のオブジェクトを再利用することで、アプリケーションがより多くのリクエストを処理できるようにします。
トレードオフ
- 複雑性:コードベースに複雑さを加え、プールの慎重な管理を必要とします。
- スレッドセーフティ:プールへの同時アクセスを慎重に処理する必要があり、同期の問題が発生する可能性があります。
- 初期化コスト:プールの初期作成にはリソースを大量に消費する可能性があります。
関連するJavaのデザインパターン
- シングルトン:プールの単一のインスタンスが使用されることを保証し、グローバルなアクセスポイントを提供します。
- フライウェイト:細かい粒度のオブジェクトを共有してメモリ使用量を削減し、オブジェクトの状態を効率的に管理することでオブジェクトプーリングを補完します。
- ファクトリーメソッド:多くの場合、プール内のオブジェクトを作成するために使用され、インスタンス化プロセスを抽象化します。