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


症状

Sun JavaTM Runtime Environment (JRETM) を使ってブラウザ内でアプレットを実行すると、次のコードに示すように、java.lang.Thread クラスの stopsuspend、または resume メソッドで java.security.AccessControlException がスローされます。

        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 Virtual Machine (VM) では何のエラーもなく実行されます。

原因

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

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

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

解決方法

Threadstopsuspend、および resume メソッドは本質的に安全ではないため、Java テクノロジでは廃止されました。

stopsuspend、および resume への呼び出しを、対象スレッドで停止、中断、再開のいずれを行うべきかを示す変数を変更するコードで置き換えます。

次の例では、stop、suspend、および 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 java.security.AccessControlException in the Sun JRE!
    }

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

アプレットの stopdestroy、および run メソッドを次のコードに示すように変更すれば、blinker.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();
        }
    }

たとえば、次のコードに示すように、blinker という名前のスレッドの状態を切り替える mousePressed イベントハンドラを含むアプレットがあるとします。

    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();
    }

イベントハンドラを次のコードで置き換えると、blinker.suspend および blinker.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 が推奨されない理由