一种选择是:
- 使用 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
。它有两个警告。首先,如果正在执行本机代码(thisObject
一帧为空),您必须等到它完成。其次,finally
块没有被调用,因此各种资源可能会泄漏。