Javaでの再帰防止とレイジーコンピューティング

少し前まで、任意の値を自動的にキャッシュおよび更新するための小さなクラスを作成しました。 使いやすさ-オーバーロードされた更新メソッドを使用して匿名クラスを作成し、必要に応じて関数を呼び出して値を廃止し、値自体を取得するだけでした:
public static void main(String[] args)
{
LazyValue<Integer> ultimateQuestionOfLife = new LazyValue<Integer>()
{
@Override
protected Integer update()
{
return findNewUltimateAnswer();
}
};

//
ultimateQuestionOfLife.invalidate();

// update()
System. out .println( "Answer is: " + ultimateQuestionOfLife.get());

// update() ,
System. out .println( "Answer is: " + ultimateQuestionOfLife.get());

//
ultimateQuestionOfLife.invalidate();

// update()
System. out .println( "Answer is: " + ultimateQuestionOfLife.get());
}


小さなエチュードプログラムでのテストは成功しましたが、このクラスを使用する大規模な作業プロジェクトでは、スタックオーバーフローエラーが発生し始めました。他の情報源なしで値が互いに更新されました。 最後の瞬間ではなく、毎回再計算が行われた場合、このエラーは発生しません。

private static LazyValue<Integer> lv1 = new LazyValue<Integer>()
{
@Override
protected Integer update()
{
return lv2.get(); //
}
};

private static LazyValue<Integer> lv2 = new LazyValue<Integer>()
{
@Override
protected Integer update()
{
return lv1.get() + 1;
}
};

public static void main(String[] args)
{
// StackOverflowException
System. out .println(lv2.get());
}


非常に迅速に、再帰を防ぐためにコードに松葉杖を挿入するのにうんざりしていたので、キャッシュクラスに再帰検出器を埋め込み、動作し、スタックを再帰の「インスティゲーター」に戻し、デフォルト値を生成することを決定しました。 StackOverflowException。 実装は次のとおりです。
private static LazyValue<Integer> lv1 = new RSLazyValue<Integer>()
{
@Override
protected Integer update()
{
return lv2.get();
}

@Override
protected Integer getDefault()
{
return 0;
}
};

private static LazyValue<Integer> lv2 = new RSLazyValue<Integer>()
{
@Override
protected Integer update()
{
return lv1.get() + 1;
}

@Override
protected Integer getDefault()
{
return 10;
}
};

public static void main(String[] args)
{
// 10
System. out .println(lv2.get());
}


すべての魔法はRSLazyValueクラスにあります(RSは再帰セーフです)。 更新が開始されると、特別なフラグが設定され、更新の最後に削除されます。 同じ関数に入って、更新が進行中の場合、再帰を末尾でキャッチし、それを使って何かをする必要があります。 額の解決策は、すぐにデフォルト値を返すことです。 ただし、処理された値は最初の関数呼び出しに戻り、外部からのみ処理された独自の値に基づいて値を返すため、これは最良の方法ではありません。 最も適切なオプションは、同じ関数の以前の「hypostasis」に呼び出しスタックを巻き戻す例外をスローし、そこからデフォルト値を返すことです。 仕組みは次のとおりです。
public ValueType get()
{
// update() ,
// , get()
// update()
if (updateInProgress)
throw new RecursionDetectedException( this );

// update()
if (isInvalid)
{
// update()
updateInProgress = true ;
try
{
//
value = update();

// isInvalid false update()
//
isInvalid = false ;
}
catch (RecursionDetectedException e)
{
if (e.getRecursionStarter() != this )
throw e; // ,
else
return getDefault(); // -
}
finally
{
// , get()
updateInProgress = false ;
}
}

return value;
}


このソリューションにより、大規模なプロジェクトで多くの時間を節約できました。 プログラマーとプロセッサーの両方の時間。 パフォーマンスの節約は、データがオンデマンドでのみ計算され、リクエストが発生しなかった場合、誰でもこのデータが必要であり、ユーザーがそれを読み取る必要がないことです。 これらの2つの小さなクラスがあなたの時間を節約することを願っています。 私は建設的な批判と「何も機能しない」場合に喜んでいるでしょう。

完全なクラスコード:
LazyValue.java
RSLazyValue.java

Source: https://habr.com/ru/post/J116082/


All Articles