我正通过ExecutorService运行多个外部方法的调用。我希望能够中断这些方法,但遗憾的是它们不会自行检查中断标志。有什么方法可以强制从这些方法中引发异常吗?
我知道从任意位置抛出异常是potentially dangerous,在我的具体情况下,我愿意抓住这个机会并准备好应对后果。
“外部方法”我的意思是来自外部库的一些方法,我无法修改它的代码(我可以,但是每当新版本发布时,这将使它成为维护的噩梦)。 / p>
外部方法计算成本高,不受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()
理论上会做我想要的,但它不仅被弃用,而且它也只适用于实际线程,而我正在使用执行器任务(也可能与未来的任务共享线程,在线程池中工作时的示例)。然而,如果没有找到更好的解决方案,我会将我的代码转换为使用老式的Threads并使用此方法。
我尝试的另一个选项是使用特殊的“Interruptable”注释来标记myMethod()
和类似方法,然后使用AspectJ(我无疑是新手)来捕获所有方法调用 - 例如:
@Before("call(* *.*(..)) && withincode(@Interruptable * *.*(..))")
public void checkInterrupt(JoinPoint thisJoinPoint) {
if (Thread.interrupted()) throw new ForcefulInterruption();
}
但withincode
不是递归匹配方法调用的方法,因此我必须将此注释编辑到外部代码中。
最后,这与a previous question of mine类似 - 虽然一个明显的区别是现在我正在处理外部库。
答案 0 :(得分:5)
我想到了以下奇怪的想法:
答案 1 :(得分:4)
这个解决方案也不容易,但它可以工作:使用Javassist或CGLIB,你可以在每个内部方法的开头插入代码(可能是由main run()方法调用的代码)来检查是否线程是活的,或者其他一些标志(如果它是其他标志,你也必须添加它,以及设置它的方法)。
我提议使用Javassist / CGLIB而不是通过代码扩展类,因为你提到它是外部的,你不想改变源代码,它可能会在未来发生变化。因此,即使内部方法名称发生更改(或其参数,返回值等),在运行时添加中断检查也适用于当前版本以及将来的版本。您只需要在每个不是run()方法的方法的开头添加类并添加中断检查。
答案 2 :(得分:2)
选项是:
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;
}
}
这是包含所有其他runnable的runnable。它存储对执行线程的引用(稍后将很重要)和一个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
块,因此可能会泄漏各种资源。
答案 3 :(得分:2)
您写道:
我尝试过的另一个选择是标记
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 * *(..)))")
之类的内容)。如果您的外部库具有可以使用within()
查明的包名称,则可能甚至不需要您的标记注释。 AspectJ非常强大,通常有多种方法可以解决问题。我建议使用它,因为它是为了你的努力而制作的。
答案 4 :(得分:1)
我已经破解了我的问题的丑陋解决方案。它不漂亮,但它适用于我的情况,所以我在这里发布它,以防它可以帮助其他人。
我所做的是 profile 我的应用程序的库部分,希望我可以隔离一小组重复调用的方法 - 例如一些get
方法或{{1或者沿着这些方向的东西;然后我可以在那里插入以下代码段:
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());
}
,则抛出的异常可以替换为库不会尝试捕获的其他内容。
幸运的是,使用VisualVM,我能够在我制作库的具体用法中找到一个非常多次的单方法。添加上面的代码段后,它现在可以正确响应中断。
这当然是不可维护的,而且没有什么能真正保证库在其他场景中会反复调用此方法;但它对我有用,而且由于分析其他应用程序并在那里插入支票相对容易,我认为这是一个通用的,如果丑陋的解决方案。
答案 5 :(得分:0)
如果内部方法具有相似的名称,那么您可以在xml(spring / AspectJ)中使用切入点定义而不是注释,因此不需要对外部库进行代码修改。
答案 6 :(得分:0)
就像mhaller所说,最好的选择是启动一个新流程。由于您的工作不合作,因此您永远不会对线程终止提供保证。
解决问题的一个很好的解决方案是使用一个支持任意暂停/停止'轻量级线程'的库,例如Akka而不是执行器服务,尽管这可能是一个有点矫枉过正。
虽然我从未使用过Akka 并且无法确认它是否按预期工作,但文档说明有一种stop()方法可以停止演员。
答案 7 :(得分:0)
据我所知,有两种使用方面的方法
AspectJ是一个自定义编译器,它拦截编译的方法(意味着它无法获得“外部方法”)。 Spring AOP(默认情况下)使用类代理在运行时拦截方法(因此它可以拦截“外部方法”。但Spring AOP的问题是它不能代理已经代理的类(AspectJ可以做什么,因为它没有代理类)。我认为AspectJ可以帮助你。