基本概念と実装手法
シングルトンの必須特性
- 単一インスタンス保証:クラスのインスタンスが1つしか存在しない
- 統一アクセスポイント:グローバルな取得メソッドを提供
- 自己初期化:クラス自身がインスタンス管理を担当
基本実装手法の比較
即時初期化(Immediate Initialization)
public class ImmediateInstance {
private static final ImmediateInstance INSTANCE = new ImmediateInstance();
private ImmediateInstance() {}
public static ImmediateInstance getSingleton() {
return INSTANCE;
}
}
特徴:
- JVMのクラスローディングでスレッドセーフ
- アプリ起動時初期化(リソース消費の可能性)
- 実装がシンプル
遅延初期化(Lazy Initialization)
public class LazyInstance {
private static LazyInstance instance;
private LazyInstance() {}
public static LazyInstance getSingleton() {
if (instance == null) {
synchronized (LazyInstance.class) {
if (instance == null) {
instance = new LazyInstance();
}
}
}
return instance;
}
}
特徴:
- 初回アクセス時にインスタンス生成
- 同期処理によるパフォーマンス影響
- 基本実装ではスレッドセーフ性の課題
Android向け最適化実装
二重チェックロック(DCL)の改良版
class SafeInstance private constructor() {
companion object {
@Volatile private var instance: SafeInstance? = null
fun getInstance(context: Context): SafeInstance {
return instance ?: synchronized(this) {
instance ?: SafeInstance().apply {
initialize(context.applicationContext)
}
}
}
private fun initialize(appContext: Context) {
// アプリケーションコンテキストでの初期化
}
}
}
利点:
- スレッドセーフ性を確保
- 初回アクセス時の遅延ロード
- 最初の初期化時のみロックを取得
静的内部クラスパターン(推奨)
public class HolderPattern {
private HolderPattern() {}
private static class Holder {
static final HolderPattern INSTANCE = new HolderPattern();
}
public static HolderPattern getSingleton() {
return Holder.INSTANCE;
}
}
動作原理:
- JVMのクラスローディングメカニズムでスレッドセーフを保証
- getInstance()呼び出し時にHolderクラスがロードされる
Android特有の利用事例
グローバル設定管理
object GlobalConfig {
private lateinit var appContext: Context
fun setup(context: Context) {
appContext = context.applicationContext
}
val apiEndpoint: String
get() = BuildConfig.API_BASE
fun getDeviceIdentifier(): String {
return Settings.Secure.getString(
appContext.contentResolver,
Settings.Secure.ANDROID_ID
)
}
}
// アプリケーション初期化
class App : Application() {
override fun onCreate() {
super.onCreate()
GlobalConfig.setup(this)
}
}
ネットワーククライアント管理
public class NetworkClient {
private static Retrofit client;
private static final String API_URL = "https://api.example.com";
private NetworkClient() {}
public static Retrofit getRetrofit() {
if (client == null) {
synchronized (NetworkClient.class) {
if (client == null) {
client = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
}
return client;
}
}
メモリ管理とパフォーマンス最適化
ライフサイクル管理の重要性
Activityコンテキストを保持するとメモリリークのリスクが発生するため、Applicationコンテキストを使用する必要がある。
マルチプロセス対応実装
public class MultiProcessSafe {
private static MultiProcessSafe instance;
public static MultiProcessSafe getInstance(Context context) {
if (instance == null) {
synchronized (MultiProcessSafe.class) {
if (instance == null && isMainProcess(context)) {
instance = new MultiProcessSafe();
}
}
}
return instance;
}
private static boolean isMainProcess(Context context) {
String processName = context.getPackageName();
int pid = android.os.Process.myPid();
ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo info : am.getRunningAppProcesses()) {
if (info.pid == pid && processName.equals(info.processName)) {
return true;
}
}
return false;
}
}
実践的な問題解決
システムサービスでのシングルトン利用の理由
- リソース制御:
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - Binder接続管理:1回の接続で済む
- パフォーマンス最適化:重いオブジェクトの重複生成を防ぐ
メモリリーク対策
- Applicationコンテキストの利用
- WeakReferenceによるActivity保持
- ライフサイクル監視によるリソース解放
DCLでのvolatileの役割
- 命令順序の最適化問題:
instance = new Singleton();が順序変更される可能性 - happens-before規則:volatileが可視性と順序を保証
- JDK5+以降でDCLが動作可能
Kotlinのobjectと手動実装の違い
| 特徴 | object宣言 | 手動シングルトン |
|---|---|---|
| スレッドセーフ | 自動保証 | 手動実装が必要 |
| 遅延初期化 | 初アクセス時 | 自由に制御可能 |
| シリアライズ対応 | 追加処理必要 | readResolveでカスタマイズ |