30

問題

ExecutorServiceを介していくつかの外部メソッドの複数の呼び出しを実行しています。これらのメソッドを中断できるようにしたいのですが、残念ながら、それらは割り込みフラグを自分でチェックしません。これらのメソッドから強制的に例外を発生させる方法はありますか?

任意の場所から例外をスローすることは潜在的に危険であることを認識しています。私の特定のケースでは、このチャンスを喜んで利用し、結果に対処する準備ができています。

詳細

「外部メソッド」とは、外部ライブラリに由来するいくつかのメソッドを意味し、そのコードを変更することはできません (できますが、新しいバージョンがリリースされるたびにメンテナンスの悪夢になります)。

外部メソッドは計算コストが高く、IO バウンドではないため、通常の割り込みには応答せず、チャネルやソケットなどを強制的に閉じることはできません。前に述べたように、割り込みフラグもチェックしません。

コードは概念的に次のようなものです。

// my code
public void myMethod() {
    Object o = externalMethod(x);
}

// External code
public class ExternalLibrary {
    public Object externalMethod(Object) {
        innerMethod1();
        innerMethod1();
        innerMethod1();
    }

    private void innerMethod1() {
        innerMethod2();
        // computationally intensive operations
    }

    private void innerMethod2() {
        // computationally intensive operations
    }
}

私が試したこと

Thread.stop()理論的には私が望むことを行いますが、それは非推奨であるだけでなく、エグゼキュータ タスクで作業している間は実際のスレッドでしか利用できません (たとえば、スレッド プールで作業する場合など、将来のタスクとスレッドを共有する可能性もあります)。 . それにもかかわらず、より良い解決策が見つからない場合は、代わりに旧式のスレッドを使用するようにコードを変換し、この方法を使用します。

私が試した別のオプションはmyMethod()、特別な「割り込み可能」アノテーションを使用して同様のメソッドをマークし、AspectJ (私は確かに初心者です) を使用して、そこですべてのメソッド呼び出しをキャッチすることです。

@Before("call(* *.*(..)) && withincode(@Interruptable * *.*(..))")
public void checkInterrupt(JoinPoint thisJoinPoint) {
    if (Thread.interrupted()) throw new ForcefulInterruption();
}

ただしwithincode、一致するメソッドによって呼び出されるメソッドには再帰的ではないため、この注釈を外部コードに編集する必要があります。

最後に、これは私の以前の質問と似ていますが、顕著な違いは、外部ライブラリを扱っていることです。

4

8 に答える 8

5

次の奇妙なアイデアが頭に浮かびます。

  • Javassist などのバイトコード変更ライブラリを使用して、バイトコード内のさまざまなポイントで割り込みチェックを導入します。これらの外部メソッドは再帰的ではないことに言及しているため、メソッドの先頭だけでは不十分な場合があるため、いつでも強制的に停止することができます。バイト コード レベルでこれを行うと、非常に応答性が高くなります。たとえば、外部コードがループなどで実行されている場合でも、割り込みチェックを導入することができます。ただし、これによりオーバーヘッドが追加されるため、全体的なパフォーマンスが低下します。
  • 外部コード用に個別のプロセス(個別の VM など) を起動します。プロセスの中止は、他のソリューションよりもはるかに簡単にコーディングできます。欠点は、IPC、ソケットなど、外部コードとコードの間に何らかの通信チャネルが必要になることです。2 つ目の欠点は、新しい VM を起動するためにより多くのリソース (CPU、メモリ) が必要になることです。環境固有。これは、外部コードを使用していくつかのタスクを開始する場合には機能しますが、数百のタスクを開始する場合には機能しません。また、パフォーマンスは低下しますが、計算自体はオリジナルと同じくらい高速になります。java.lang.Process.destroy() を使用して、プロセスを強制的に停止できます。
  • 各 checkXXX メソッドで割り込みチェックを実行するカスタム SecurityManager を使用します。外部コードが何らかの方法で特権メソッドを呼び出す場合は、これらの場所で中止するだけで十分な場合があります。外部コードがシステム プロパティを定期的に読み取る場合の例は、java.lang.SecurityManager.checkPropertyAccess(String) です。
于 2010-12-28T23:06:47.763 に答える
4

この解決策も簡単ではありませんが、うまくいく可能性があります: Javassist または CGLIB を使用すると、各内部メソッド (おそらくメインの run() メソッドによって呼び出されるもの) の先頭にコードを挿入して、スレッドが生きているかどうかを確認できます。 、またはその他のフラグ (その他のフラグの場合は、それを設定するメソッドと共に追加する必要があります)。

コードを介してクラスを拡張する代わりに、Javassist/CGLIB を提案しています。これは、外部であり、ソース コードを変更したくないと述べており、将来変更される可能性があるためです。したがって、実行時に割り込みチェックを追加すると、内部メソッド名 (またはそのパラメーター、戻り値など) が変更された場合でも、現在のバージョンと将来のバージョンで機能します。クラスを取得して、run() メソッドではない各メソッドの先頭に割り込みチェックを追加するだけです。

于 2010-12-28T21:58:50.977 に答える
2

あなたが書いた:

私が試したもう1つのオプションはmyMethod()、特別な「Interruptable」アノテーションを使用して同様のメソッドをマークし、AspectJ(私は確かに初心者です)を使用して、そこですべてのメソッド呼び出しをキャッチすることです。

@Before("call(* *.*(..)) && withincode(@Interruptable * *.*(..))")
public void checkInterrupt(JoinPoint thisJoinPoint) {
    if (Thread.interrupted()) throw new ForcefulInterruption();
}

ただしwithincode、一致するメソッドによって呼び出されるメソッドには再帰的ではないため、このアノテーションを外部コードに編集する必要があります。

AspectJのアイデアは良いですが、あなたはする必要があります

  • cflow()またはを使用cflowbelow()して、特定の制御フローを再帰的に一致させます(例:のようなもの@Before("cflow(execution(@Interruptable * *(..)))"))。
  • 自分のコードだけでなく、外部ライブラリも織り込むようにしてください。これは、バイナリウィービングを使用して、JARファイルのクラスをインストルメント化して新しいJARファイルに再パッケージ化するか、アプリケーションの起動時(つまり、クラスのロード中)にLTW(ロード時ウィービング)を適用することによって実行できます。

外部ライブラリに。で特定できるパッケージ名がある場合は、マーカー注釈さえ必要ない場合がありますwithin()。AspectJは非常に強力であり、多くの場合、問題を解決する方法は複数あります。あなたのような努力のために作られたので、私はそれを使うことをお勧めします。

于 2012-11-09T09:43:11.150 に答える
2

オプションは次のとおりです。

  1. JDI を使用して、VM をそれ自体に接続します。
  2. タスクを実行しているスレッドを調べます。これは簡単ではありませんが、すべてのスタック フレームにアクセスできるため、実行可能です。(タスク オブジェクトに一意のidフィールドを配置すると、それを実行しているスレッドを識別できます。)
  3. スレッドを非同期で停止します。

停止したスレッドがエグゼキューターに深刻な影響を与えるとは思いませんが (結局のところ、それらはフェイルセーフである必要があります)、スレッドの停止を伴わない代替ソリューションがあります。

タスクがシステムの他の部分で何も変更しない場合 (これは公正な仮定であり、そうでなければそれらを撃墜しようとしないでしょう)、できることは、JDI を使用して不要なスタック フレームをポップオフし、タスクを正常に終了します。

public class StoppableTask implements Runnable {

private boolean stopped;
private Runnable targetTask;
private volatile Thread runner;
private String id;

public StoppableTask(TestTask targetTask) {
    this.targetTask = targetTask;
    this.id = UUID.randomUUID().toString();
}

@Override
public void run() {
    if( !stopped ) {
        runner = Thread.currentThread();
        targetTask.run();
    } else {
        System.out.println( "Task "+id+" stopped.");
    }
}

public Thread getRunner() {
    return runner;
}

public String getId() {
    return id;
}
}

これは、他のすべてのランナブルをラップするランナブルです。実行中のスレッドへの参照 (後で重要になります) と ID が格納されるため、JDI 呼び出しで見つけることができます。

public class Main {

public static void main(String[] args) throws IOException, IllegalConnectorArgumentsException, InterruptedException, IncompatibleThreadStateException, InvalidTypeException, ClassNotLoadedException {
    //connect to the virtual machine
    VirtualMachineManager manager = Bootstrap.virtualMachineManager();
    VirtualMachine vm = null;
    for( AttachingConnector con : manager.attachingConnectors() ) {
        if( con instanceof SocketAttachingConnector ) {
            SocketAttachingConnector smac = (SocketAttachingConnector)con;
            Map<String,? extends Connector.Argument> arg = smac.defaultArguments();
            arg.get( "port" ).setValue( "8000");
            arg.get( "hostname" ).setValue( "localhost" );
            vm = smac.attach( arg );
        }
    }

    //start the test task
    ExecutorService service = Executors.newCachedThreadPool();
    StoppableTask task = new StoppableTask( new TestTask() );
    service.execute( task );
    Thread.sleep( 1000 );

    // iterate over all the threads
    for( ThreadReference thread : vm.allThreads() ) {
        //iterate over all the objects referencing the thread
        //could take a long time, limiting the number of referring
        //objects scanned is possible though, as not many objects will
        //reference our runner thread
        for( ObjectReference ob : thread.referringObjects( 0 ) ) {
            //this cast is safe, as no primitive values can reference a thread
            ReferenceType obType = (ReferenceType)ob.type();
            //if thread is referenced by a stoppable task
            if( obType.name().equals( StoppableTask.class.getName() ) ) {

                StringReference taskId = (StringReference)ob.getValue( obType.fieldByName( "id" ));

                if( task.getId().equals( taskId.value() ) ) {
                    //task with matching id found
                    System.out.println( "Task "+task.getId()+" found.");

                    //suspend thread
                    thread.suspend();

                    Iterator<StackFrame> it = thread.frames().iterator();
                    while( it.hasNext() ) {
                        StackFrame frame = it.next();
                        //find stack frame containing StoppableTask.run()
                        if( ob.equals( frame.thisObject() ) ) {
                            //pop all frames up to the frame below run()
                            thread.popFrames( it.next() );
                            //set stopped to true
                            ob.setValue( obType.fieldByName( "stopped") , vm.mirrorOf( true ) );
                            break;
                        }
                    }
                    //resume thread
                    thread.resume();

                }

            }
        }
    }

}
}

参考までに、私がテストした「ライブラリ」呼び出しは次のとおりです。

public class TestTask implements Runnable {

    @Override
    public void run() {
        long l = 0;
        while( true ) {
            l++;
            if( l % 1000000L == 0 )
                System.out.print( ".");
        }

    }
}

Mainコマンド ライン オプションを使用してクラスを起動することで、それを試すことができます-agentlib:jdwp=transport=dt_socket,server=y,address=localhost:8000,timeout=5000,suspend=n。2 つの注意点があります。まず、実行中のネイティブ コードがある場合 (thisObjectフレームの が null の場合)、終了するまで待機する必要があります。次に、finallyブロックが呼び出されないため、さまざまなリソースがリークする可能性があります。

于 2011-04-05T16:13:29.517 に答える
1

私は自分の問題に対する醜い解決策をハックしました。きれいではありませんが、私の場合はうまくいくので、他の人に役立つ場合に備えてここに投稿しています.

私がしたことは、アプリケーションのライブラリ部分をプロファイリングすることでした。たとえば、これらの行に沿っていくつかのgetメソッドや何かなど、繰り返し呼び出されるメソッドの小さなグループを分離できることを期待しています。equals()そして、そこに次のコード セグメントを挿入できます。

if (Thread.interrupted()) {
    // Not really necessary, but could help if the library does check it itself in some other place:
    Thread.currentThread().interrupt();
    // Wrapping the checked InterruptedException because the signature doesn't declare it:
    throw new RuntimeException(new InterruptedException());
}

ライブラリのコードを編集して手動で挿入するか、適切なアスペクトを記述して自動的に挿入します。ライブラリが をキャッチして飲み込もうとした場合RuntimeException、スローされた例外は、ライブラリがキャッチしようとしない別のものに置き換えられる可能性があることに注意してください。

私にとって幸運なことに、VisualVMを使用して、ライブラリの特定の使用中に非常に多くの回数呼び出された単一のメソッドを見つけることができました。上記のコード セグメントを追加すると、割り込みに適切に応答するようになりました。

もちろん、これは維持できません。さらに、ライブラリが他のシナリオでこのメソッドを繰り返し呼び出すことを保証するものは何もありません。しかし、それは私にとってはうまくいきました。他のアプリケーションをプロファイリングしてそこにチェックを挿入するのは比較的簡単なので、これは醜い場合でも一般的な解決策だと思います。

于 2011-04-09T08:48:09.007 に答える
0

私の知る限り、アスペクトを使用するには2つの方法があります

  • AspecJ
  • 春のAOP

AspectJ は、コンパイルされたメソッドをインターセプトするカスタマイズされたコンパイラです (つまり、「外部メソッド」を取得できません。Spring AOP (デフォルト) は、クラス プロキシを使用して実行時にメソッドをインターセプトします (したがって、「外部メソッド」をインターセプトできます)。しかし、問題はSpring AOP では、すでにプロキシされているクラスをプロキシできないということです (AspectJ はクラスをプロキシしないため、これを行うことができます)。

于 2012-11-06T20:50:31.963 に答える
0

内部メソッドの名前が似ている場合は、注釈の代わりに xml (spring/AspectJ) でポイントカット定義を使用できるため、外部ライブラリのコード変更は必要ありません。

于 2010-12-28T09:11:13.480 に答える
0

mhallerが言ったように、最良の選択肢は新しいプロセスを開始することです。あなたの仕事はそれほど協力的ではないので、スレッドの終了を保証することはできません。

問題の良い解決策は、エグゼキュータ サービスの代わりにAkkaなどの「軽量スレッド」の任意の一時停止/停止をサポートするライブラリを使用することですが、これは少しやり過ぎかもしれません。

私は Akka を使用したことがなく、期待どおりに動作することを確認できませんが、ドキュメントには、アクターを停止するためのstop()メソッドがあると記載されています。

于 2011-04-05T10:57:22.890 に答える