使用Thread.stop()小心锁定但不可信的代码

chi*_*ity 11 java multithreading

我知道这Thread.stop()是被弃用的,并且有充分的理由:它通常不安全.但这并不意味着它永远不会安全......据我所知,它在我想要使用它的环境中是安全的; 而且,据我所见,我别无选择.

上下文是双人战略游戏的第三方插件:国际象棋将作为工作示例.第三方代码需要被赋予当前的董事会状态,并且(比方说)10秒来决定其移动.它可以返回其移动并在允许的时间内终止,或者它可以在任何时候发出信号,指示其当前的首选移动; 如果时间限制到期,则应该在其轨道上停止,并且应该播放其最近的首选移动.

编写插件以便在请求时正常停止不是一个选项:我需要能够采取任意不受信任的第三方插件.所以我必须有一些强行终止它的方法.

这是我正在做的锁定它:

  1. 插件的类被放入他们自己的线程,进入他们自己的线程组.
  2. 它们加载了一个具有高度限制性的类加载器SecurityManager:所有类都可以进行数字运算.
  3. 这些类不会引用任何其他线程,因此它们不能用于Thread.join()任何未创建的线程.
  4. 插件只从主机系统获得对对象的两个引用.一个是国际象棋棋盘; 这是一个深层拷贝,之后会被丢弃,所以如果它进入一个不一致的状态并不重要.另一个是一个简单的对象,允许插件设置其当前首选的移动.
  5. 我正在检查CPU时间是否超过限制,并且我定期检查插件的至少一个线程是否正在执行某些操作(这样它无法无限期地睡眠并避免CPU时间达到限制).
  6. 首选的移动没有​​足够的状态不一致,但无论如何它在返回后都会被仔细地和防御性地克隆,并且丢弃返回的移动.通过这一点,有没有什么遗留在主机系统到插件有一个参考:没有线程,没有对象实例.

因此,似乎插件不能将任何东西保持在不一致的状态(除了它可能创建的任何对象,然后将被丢弃); 并且它不会影响任何其他线程(除了它产生的任何线程,它们将是相同的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 3

虽然我可以想象这样的场景,您仔细审查过的代码可以安全地停止Thread.stop(),但您正在谈论相反的情况,您非常不信任的代码,以至于您试图通过 来限制其操作SecurityManager

\n\n

那么会出现什么问题呢?首先,停止并不比中断更可靠。虽然更容易忽略中断,但简单的中断catch(Throwable t){}就足以使其Thread.stop()不再工作,catch即使没有阻止工作的意图,这样的中断也可能出现在代码中Thread.stop(),尽管这是糟糕的编码风格。但是想要忽略的程序员甚至可能会故意Thread.stop()添加一个catch(Throwable t){}和/或。catch(ThreadDeath t){}

\n\n

说到糟糕的编码风格,应该始终使用锁定try \xe2\x80\xa6 finally \xe2\x80\xa6来确保即使在特殊情况下也能保证释放锁。如果停止的代码未能正确执行此操作,则当线程停止时锁可能会保持锁定状态。

\n\n

最重要的是,在同一个 JVM 中,您无法\xe2\x80\x99 保护自己免受不受信任的代码的侵害。JVM一个保护外界的沙箱。仅举一个简单的例子:恶意代码可能会通过执行大量小内存分配来开始耗尽内存。在 JVM 中您无法采取任何措施来保护在同一 JVM 中运行的某些代码免受攻击。您只能对整个 JVM 实施限制。

\n\n

所以唯一的解决方案是在不同的 JVM 中运行插件。

\n