java.security.AccessControlExceptionjava.lang.Threadstopsuspend、または resume メソッドでスローされる


症状

アプレットを Sun JRE を使用したブラウザで実行している場合、AccessControlExceptionjava.lang.Threadstopsuspend、または resume メソッドでスローされます。

        java.security.AccessControlException: access denied (java.lang.RuntimePermission modifyThread)
        at java.security.AccessControlContext.checkPermission(Unknown Source)
        at java.security.AccessController.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkPermission(Unknown Source)
        at sun.applet.AppletSecurity.checkAccess(Unknown Source)
        at java.lang.Thread.checkAccess(Unknown Source)
        at java.lang.Thread.stop(Unknown Source)
        at ....


同じアプレットが Microsoft VM では実行できます。

原因

この例外は、Sun JRE でこれらのメソッドが死んでいる Thread オブジェクト上で呼び出されるために起こります。

Sun JRE の Java クラスライブラリは時間の経過とともに変化してきました。詳細になった API もあれば、廃止された API もあります。また、実装が変更された API もあります。

stopsuspend、および resume を死んでいる Thread オブジェクト上で呼び出した場合の結果は詳細に定義されていなかったため、Microsoft VM では無操作となります。しかし Sun JRE ではこれらのメソッドを死んでいる Thread オブジェクトで呼び出すと、基礎となる実装の不変部分が意味をなさなくなるため、AccessControlException がスローされます。

解決方法

Threadstopsuspend、および resume メソッドは本質的に安全ではないため、Java 2 プラットフォームでは廃止されました。

この問題を回避するには、ターゲットスレッドが停止・中断・再開することを示す変数を変更するコードで、stopsuspend、および resume の呼び出しを置き換えます。

たとえば、アプレットに次のメソッドが含まれているとします。

    private Thread blinker;

    public void start() {
        blinker = new Thread(this);
        blinker.start();
    }

    public void stop() {
        blinker.stop();  // UNSAFE!
    }
    public void destroy() {
        blinker.stop();  // UNSAFE and WILL throw AccessControlException in the Sun JRE!
    }

    public void run() {
        Thread thisThread = Thread.currentThread();
        while (true) {
            try {
                thisThread.sleep(interval);
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

アプレットの stopdestroy、および run メソッドを次のコードで置き換えれば、Thread.stop を使用しないで済みます。

    private volatile Thread blinker;

    public void stop() {
        blinker = null;
    }

    public void destroy() {
        blinker = null;
    }

    public void run() {
        Thread thisThread = Thread.currentThread();
        while (blinker == thisThread) {
            try {
                thisThread.sleep(interval);
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

別の例: アプレットに次のような mousePressed イベントハンドラが含まれており、それが blinker というスレッドの状態を切り替えるとします。

    private boolean threadSuspended;

    public void mousePressed(MouseEvent e) {
        e.consume();

        if (threadSuspended)
            blinker.resume();
        else
            blinker.suspend();  // DEADLOCK-PRONE!

        threadSuspended = !threadSuspended;
    }

    public void run()
    {
	 while (true) {
        try {
            Thread.currentThread().sleep(interval);
        } catch (InterruptedException e){
        }
        repaint();
    }

上のイベントハンドラを次のコードで置き換えると、Thread.suspend および Thread.resume を使わなくて済みます。

    private boolean volatile threadSuspended;

    public synchronized void mousePressed(MouseEvent e) {
        e.consume();

        threadSuspended = !threadSuspended;

        if (!threadSuspended)
            notify();
    }

    public void run() {
        while (true) {
            try {
                Thread.currentThread().sleep(interval);

                if (threadSuspended) {
                    synchronized(this) {
                        while (threadSuspended)
                            wait();
                    }
                }
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

詳細情報

Thread.stopThread.suspendThread.resume、および Runtime.runFinalizersOnExit が推奨されない理由