オプションは次のとおりです。
- JDI を使用して、VM をそれ自体に接続します。
- タスクを実行しているスレッドを調べます。これは簡単ではありませんが、すべてのスタック フレームにアクセスできるため、実行可能です。(タスク オブジェクトに一意の
id
フィールドを配置すると、それを実行しているスレッドを識別できます。)
- スレッドを非同期で停止します。
停止したスレッドがエグゼキューターに深刻な影響を与えるとは思いませんが (結局のところ、それらはフェイルセーフである必要があります)、スレッドの停止を伴わない代替ソリューションがあります。
タスクがシステムの他の部分で何も変更しない場合 (これは公正な仮定であり、そうでなければそれらを撃墜しようとしないでしょう)、できることは、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
ブロックが呼び出されないため、さまざまなリソースがリークする可能性があります。