6

残念ながら、Java で String に対して正規表現を使用する場合、タイムアウトを指定する方法はありません。したがって、どのパターンがどの入力に適用されるかを厳密に制御できない場合、(あまり適切に設計されていない) パターンを (悪意のある?) 入力に一致させようと無限に試行しながら、大量の CPU を消費するスレッドが発生する可能性があります。

Thread#stop() が廃止された理由を認識しています ( http://download.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.htmlを参照)。それらは、ThreadDeath 例外の場合に破損する可能性があり、実行中の JVM 環境を汚染し、微妙なエラーにつながる可能性があるオブジェクトを中心にしています。

JVM の仕組みについて私よりも深い洞察を持っている人への私の質問は次のとおりです。停止する必要があるスレッドが、プログラムの残りの部分で使用されるオブジェクトに対する (明らかな) モニターまたは参照を保持していない場合、それにもかかわらず、Thread#stop() を使用することは許容できますか?

正規表現のマッチングをタイムアウトで処理できるように、かなり防御的なソリューションを作成しました。コメントやコメントをいただければ幸いです。特に、回避しようと努力しているにも関わらず、このアプローチが引き起こす可能性のある問題について。

ありがとう!

import java.util.concurrent.Callable;

public class SafeRegularExpressionMatcher {

    // demonstrates behavior for regular expression running into catastrophic backtracking for given input
    public static void main(String[] args) {
        SafeRegularExpressionMatcher matcher = new SafeRegularExpressionMatcher(
                "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "(x+x+)+y", 2000);
        System.out.println(matcher.matches());
    }

    final String stringToMatch;

    final String regularExpression;

    final int timeoutMillis;

    public SafeRegularExpressionMatcher(String stringToMatch, String regularExpression, int timeoutMillis) {
        this.stringToMatch = stringToMatch;
        this.regularExpression = regularExpression;
        this.timeoutMillis = timeoutMillis;
    }

    public Boolean matches() {
        CallableThread<Boolean> thread = createSafeRegularExpressionMatchingThread();
        Boolean result = tryToGetResultFromThreadWithTimeout(thread);
        return result;
    }

    private CallableThread<Boolean> createSafeRegularExpressionMatchingThread() {
        final String stringToMatchForUseInThread = new String(stringToMatch);
        final String regularExpressionForUseInThread = new String(regularExpression);
        Callable<Boolean> callable = createRegularExpressionMatchingCallable(stringToMatchForUseInThread,
                regularExpressionForUseInThread);
        CallableThread<Boolean> thread = new CallableThread<Boolean>(callable);
        return thread;
    }

    private Callable<Boolean> createRegularExpressionMatchingCallable(final String stringToMatchForUseInThread,
            final String regularExpressionForUseInThread) {
        Callable<Boolean> callable = new Callable<Boolean>() {
            public Boolean call() throws Exception {
                return Boolean.valueOf(stringToMatchForUseInThread.matches(regularExpressionForUseInThread));
            }
        };
        return callable;
    }

    private Boolean tryToGetResultFromThreadWithTimeout(CallableThread<Boolean> thread) {
        startThreadAndApplyTimeout(thread);
        Boolean result = processThreadResult(thread);
        return result;
    }

    private void startThreadAndApplyTimeout(CallableThread<Boolean> thread) {
        thread.start();
        try {
            thread.join(timeoutMillis);
        } catch (InterruptedException e) {
            throwRuntimeException("Interrupt", e);
        }
    }

    private Boolean processThreadResult(CallableThread<Boolean> thread) {
        Boolean result = null;
        if (thread.isAlive()) {
            killThread(thread); // do not use anything from the thread anymore, objects may be damaged!
            throwRuntimeException("Timeout", null);
        } else {
            Exception exceptionOccurredInThread = thread.getException();
            if (exceptionOccurredInThread != null) {
                throwRuntimeException("Exception", exceptionOccurredInThread);
            } else {
                result = thread.getResult();
            }
        }
        return result;
    }

    private void throwRuntimeException(String situation, Exception e) {
        throw new RuntimeException(situation + " occured while applying pattern /" + regularExpression + "/ to input '"
                + stringToMatch + " after " + timeoutMillis + "ms!", e);
    }

    /**
     * This method uses {@link Thread#stop()} to kill a thread that is running wild. Although it is acknowledged that
     * {@link Thread#stop()} is inherently unsafe, the assumption is that the thread to kill does not hold any monitors on or
     * even references to objects referenced by the rest of the JVM, so it is acceptable to do this.
     * 
     * After calling this method nothing from the thread should be used anymore!
     * 
     * @param thread Thread to stop
     */
    @SuppressWarnings("deprecation")
    private static void killThread(CallableThread<Boolean> thread) {
        thread.stop();
    }

    private static class CallableThread<V> extends Thread {

        private final Callable<V> callable;

        private V result = null;

        private Exception exception = null;

        public CallableThread(Callable<V> callable) {
            this.callable = callable;
        }

        @Override
        public void run() {
            try {
                V result = compute();
                setResult(result);
            } catch (Exception e) {
                exception = e;
            } catch (ThreadDeath e) {
                cleanup();
            }
        }

        private V compute() throws Exception {
            return callable.call();
        }

        private synchronized void cleanup() {
            result = null;
        }

        private synchronized void setResult(V result) {
            this.result = result;
        }

        public synchronized V getResult() {
            return result;
        }

        public synchronized Exception getException() {
            return exception;
        }

    }

}

編集:

このソリューションを教えてくれたdawceのおかげで、追加のスレッドを必要とせずに元の問題を解決できました。そこにコードを投稿しました。回答してくれたすべての人に感謝します。

4

4 に答える 4

9

利用可能な唯一の解決策であると判断した場合は、 Thread.stop() を使用できます。アプリケーションが良好な状態であることを確認するために、アプリケーションをシャットダウンして再起動する必要がある場合があります。

注: スレッドはキャプチャして無視できるThreadDeathため、停止してもすべてのスレッドが停止するとは限りません。

スレッドを停止する別の方法は、別のプロセスで実行することです。これは必要に応じて殺すことができます。これにより、リソースが一貫性のない状態 (ロック ファイルなど) のままになる可能性はありますが、その可能性は低くなり、制御が容易になります。

もちろん、最善の解決策は、コードを修正して、最初からこれを行わず、代わりに Thread.interrupt() を尊重することです。

于 2012-07-03T09:07:05.003 に答える
2

停止する必要のあるスレッドが、プログラムの残りの部分で使用されるオブジェクトの(明らかな)モニターまたはオブジェクトへの参照を保持していない場合でも、Thread#stop()を使用しても問題ありませんか?

それが「許容できる」かどうかを決めるのはあなた次第です。私たちにできることは、それが安全かどうかをアドバイスすることだけです。そして答えはそうではないということです。

  • それが保持する非自明なモニターと参照はどうですか?

  • そうでなければ作成する通知などはどうですか?

  • そうでなければ静力学に影響を与える可能性のあるアクションはどうですか?

問題は、スレッドがアプリケーションの他の部分と行う可能性のあるすべての相互作用を考慮したことを確実に知ることが(ほとんどの場合)難しいことです。


アプリケーションの再起動は、まさに私が避けようとしていることです...

それがあなたの問題の本当の根源だと私は思います。つまり、実用的な理由で長時間実行されるプログラムを再起動する必要があるという事実を考慮せずにプログラムを設計しました。潜在的なバグがある特に複雑なもの。

于 2012-07-03T09:59:46.380 に答える
2

whichを使用する代わりに、非Thread.stop()推奨になったものを使用Thread.interrupt()ますisInterrupted()interrupted()InterruptedException

Thread クラスを拡張して構築するための私のパターンは次のようなものです

 class MyThread extends Thread{
      private volatile boolean keepRunning = true;

      public void run(){
           while(keepRunning){
                // do my work
           }
       }

       public void killThread(){
           keepRunning = false;
           this.interrupt();
       }
 }

私の扱い方が完璧だと言っているわけではありません。もっと良い方法があるかもしれませんが、これは私にとってはうまくいきます。

于 2012-07-03T09:05:33.327 に答える
1

ロックなどを保持しないようにスレッドコードを具体的に設計する場合 (はい、これには非明示的なロックが含まれます。たとえば、文字列のサイズを変更するときに使用される可能性のある malloc ロック)、スレッドを停止します。「中断された」フラグをポーリングすることは問題ありませんが、「中断された」フラグをポーリングすることを意味します。実際には設定されていない 99.9999% の時間のオーバーヘッド。これは、高性能のタイトなループで問題になる可能性があります。

チェックを最も内側のループから除外し、妥当な頻度でチェックできる場合は、それが最善の方法です。

フラグを頻繁にチェックできない場合 (たとえば、アクセスできないライブラリ コードのタイトなループが原因で)、スレッドの優先度を可能な限り低く設定し、最終的に終了するまでそれを忘れることができます。

スレッドが動作しているデータを破棄して、ライブラリ コードが正常に終了し、例外が発生して、コントロールが不透明なライブラリ コードから抜け出すか、'OnError ' 呼び出されるハンドラー。ライブラリの場合。文字列を操作している場合、文字列をヌルでスプラッティングすると、必ず何かが行われます。どんな例外でも構いません - スレッドで AV/segfault を調整できる場合は、制御を取り戻す限り問題ありません。

于 2012-07-03T11:34:23.157 に答える