Я знаю, что Thread.stop()
устарел, и по уважительной причине: он, в общем, не безопасен. Но это не означает, что это никогда не безопасно... насколько я могу судить, это безопасно в контексте, в котором я хочу его использовать; и, насколько я вижу, у меня нет другого выбора.
Контекст - сторонний плагин для стратегии игры с двумя игроками: шахматы будут работать как рабочий пример. Для стороннего кода необходимо предоставить текущее состояние платы и (скажем) 10 секунд, чтобы принять решение о его движении. Он может возвращать свое движение и заканчиваться в течение допустимого времени, или он может, когда захочет, сигнализировать о своем текущем предпочтительном ходу; если срок истекает, он должен быть остановлен на своих дорожках, и его последний предпочтительный ход должен быть воспроизведен.
Написание плагина для прерывания изящно по запросу не опция: мне нужно иметь возможность использовать произвольные ненадежные сторонние плагины. Поэтому у меня должен быть какой-то способ насильственного прекращения его.
Вот что я делаю, чтобы заблокировать его:
- Классы для плагина помещаются в собственный поток в свою группу потоков.
- Они загружаются загрузчиком классов, который имеет строго ограничивающий
SecurityManager
на месте: все классы могут выполнять операции с номером. - Классы не получают ссылки на любые другие потоки, поэтому они не могут использовать
Thread.join()
для всего, что они не создали. - Плагин получает только две ссылки на объекты из хост-системы. Одним из них является состояние шахматной доски; это глубокая копия, и впоследствии она выбрасывается, поэтому не имеет значения, попадает ли она в противоречивое состояние. Другой - простой объект, который позволяет плагину устанавливать текущий предпочтительный ход.
- Я проверяю, превышено ли время 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);
}
}