【发布时间】:2014-08-25 15:51:10
【问题描述】:
我知道Thread.stop() 已被弃用,这是有充分理由的:一般来说,它并不安全。但这并不意味着它是从不安全的......据我所知,它在我想要使用它的上下文中是安全的;而且,据我所知,我别无选择。
上下文是用于两人战略游戏的第三方插件:国际象棋将作为工作示例。需要为第三方代码提供当前的棋盘状态,并且(例如)10 秒来决定其移动。它可以返回它的移动并在允许的时间内终止,或者它可以在任何时候发出它当前首选移动的信号;如果时间限制到期,它应该停止在它的轨道上,并且应该播放它最近的首选移动。
编写插件以根据请求正常停止是不是一种选择:我需要能够采用任意不受信任的第三方插件。所以我必须有一些方法来强制终止它。
这是我正在做的事情来锁定它:
- 插件的类被放入它们自己的线程中,放入它们自己的线程组中。
- 它们加载了一个类加载器,该类加载器具有高度限制性的
SecurityManager:所有类都可以做的就是数字运算。 - 这些类没有获得对任何其他线程的引用,因此它们不能在它们尚未创建的任何东西上使用
Thread.join()。 - 插件仅从主机系统中获得两个对对象的引用。一是棋盘状态;这是一个深拷贝,之后会被丢弃,所以如果它进入不一致的状态也没关系。另一个是允许插件设置其当前首选移动的简单对象。
- 我正在检查 CPU 时间何时超过限制,并且我会定期检查插件的至少一个线程是否正在做某事(这样它就不能无限期地休眠并避免 CPU 时间达到限制)。
- 首选招式没有足够的状态导致不一致,但无论如何它在返回后会被小心且防御性地克隆,并且返回的那个被丢弃。至此,主机系统中什么都没有可供插件引用:没有线程,没有对象实例。
因此,插件似乎不能让任何东西处于不一致的状态(除了它可能创建的任何对象,然后这些对象将被丢弃);并且它不会影响任何其他线程(除了它产生的任何线程,它们将在同一个ThreadGroup 中,因此也会被杀死)。
在我看来,Thread.stop() 被弃用的原因似乎不适用于此处(按设计)。我错过了什么吗?这里还有危险吗?还是我把事情隔离得足够仔细,不会有问题?
还有更好的方法吗?我认为唯一的选择是启动一个全新的 JVM 来运行不受信任的代码,并在不再需要该进程时强行终止该进程,但这还有一千个其他问题(昂贵、脆弱、依赖于操作系统)。
请注意:我对“哦,它已被弃用是有原因的,你想看,伙计”这样的回答不感兴趣。我知道它已被弃用是有原因的,我完全理解为什么一般从笼子里放出来是不安全的。我要问的是是否有特定原因认为在这种情况下不安全。
对于它的价值,这里是代码的(删节)相关位:
public void playMoveInternal(GameState game) throws IllegalMoveException,
InstantiationException, IllegalAccessException,
IllegalMoveSpecificationException {
ThreadGroup group = new ThreadGroup("playthread group");
Thread playthread = null;
group.setMaxPriority(Thread.MIN_PRIORITY);
GameMetaData meta = null;
StrategyGamePlayer player = null;
try {
GameState newgame = (GameState) game.clone();
SandboxedURLClassLoader loader = new SandboxedURLClassLoader(
// recreating this each time means static fields don't persist
urls[newgame.getCurPlayer() - 1], playerInterface);
Class<?> playerClass = loader.findPlayerClass();
GameTimer timer = new GameTimer(
newgame.getCurPlayer() == 1 ? timelimit : timelimit2);
// time starts ticking here!
meta = new GameMetaData((GameTimer) timer.clone());
try {
player = (StrategyGamePlayer) playerClass.newInstance();
} catch (Exception e) {
System.err.println("Couldn't create player module instance!");
e.printStackTrace();
game.resign(GameMoveType.MOVE_ILLEGAL);
return;
}
boolean checkSleepy = true;
playthread = new Thread(group, new MoveMakerThread(player, meta,
newgame), "MoveMaker thread");
int badCount = 0;
playthread.start();
try {
while ((timer.getTimeRemaining() > 0) && (playthread.isAlive())
&& (!stopping) && (!forceMove)) {
playthread.join(50);
if (checkSleepy) {
Thread.State thdState = playthread.getState();
if ((thdState == Thread.State.TIMED_WAITING)
|| (thdState == Thread.State.WAITING)) {
// normally, main thread will be busy
Thread[] allThreads = new Thread[group
.activeCount() * 2];
int numThreads = group.enumerate(allThreads);
boolean bad = true;
for (int i = 0; i < numThreads; i++) {
// check some player thread somewhere is doing something
thdState = allThreads[i].getState();
if ((thdState != Thread.State.TIMED_WAITING)
&& (thdState != Thread.State.WAITING)) {
bad = false;
break; // found a good thread, so carry on
}
}
if ((bad) && (badCount++ > 100))
// means player has been sleeping for an expected 5
// sec, which is naughty
break;
}
}
}
} catch (InterruptedException e) {
System.err.println("Interrupted: " + e);
}
} catch (Exception e) {
System.err.println("Couldn't play the game: " + e);
e.printStackTrace();
}
playthread.destroy();
try {
Thread.sleep(1000);
} catch (Exception e) {
}
group.stop();
forceMove = false;
try {
if (!stopping)
try {
if (!game.isLegalMove(meta.getBestMove())) {
game.resign(GameMoveType.MOVE_ILLEGAL);
}
else
game.makeMove((GameMove) (meta.getBestMove().clone()));
// We rely here on the isLegalMove call to make sure that
// the return type is the right (final) class so that the clone()
// call can't execute dodgy code
} catch (IllegalMoveException e) {
game.resign(GameMoveType.MOVE_ILLEGAL);
} catch (NullPointerException e) {
// didn't ever choose a move to make
game.resign(GameMoveType.MOVE_OUT_OF_TIME);
}
} catch (Exception e) {
e.printStackTrace();
game.resign(GameMoveType.MOVE_OUT_OF_TIME);
}
}
【问题讨论】:
-
对于您正在尝试做的事情,使用 ExecutorService 可能是一个更优雅的解决方案。
-
看起来这在某些方面可能更干净一些,但它不会解决根本问题。
ExecutorService的 Javadoc 说:“除了尽力停止处理正在执行的任务之外,没有任何保证。例如,典型的实现将通过Thread.interrupt()取消,因此任何无法响应中断的任务都可能永远不会终止。” -
一个插件仍然可能会扰乱 JDK 内部的状态(如果有人想要恶意),并且您的 ClassLoader 可能不会加载另一组 JDK 类?你需要下定决心:你的方法对于防御大多数 sloppy 插件来说听起来很实用。但是有人想搞砸虚拟机,他们可能还是找到了办法(毕竟他们从来没有设法堵住小程序的所有漏洞)。你的目标是什么?
-
@Durandal 我确实想知道这一点。即使在限制性安全管理器下,JVM 中是否存在任何类都可以进入和离开的同步对象?你有什么例子吗?我想不出来。
-
“首选动作没有足够的状态来导致不一致,但是……在它返回后……被防御性克隆”这没有任何意义。如果它是您可以克隆的东西,它也可能是不一致的。在停止线程的上下文中可以安全使用的东西并不多。一旦它包含多个变量,或者它不是原始值或真正不可变的对象,它就会变得不一致。对于原始值或真正不可变的对象,克隆毫无意义。
标签: java multithreading