7

以下の単純な midlet コード (クラス Moo) (抜粋の後) はデッドロックします (少なくとも、スレッドに関するこの投稿を読んだ後はデッドロックになると思います)。

投稿から関連する抜粋を再現しました:


    String url = ...
    Connection conn = null;

    try {
        conn = Connector.open( url );
        // do something here
    }
    catch( IOException e ){
        // error
    }

問題の根本は、open() 呼び出しのブロッキング性にあります。一部のプラットフォームでは、システムが実際の接続を内部で、別のスレッドと同等に行います。接続スレッドが接続を確立するまで、呼び出しスレッドはブロックされます。同時に、セキュリティ サブシステムはユーザーに接続の確認を要求する場合があり、イベント スレッドがユーザーから確認を取得するまで接続スレッドはブロックされます。イベント スレッドが既に接続スレッドを待機しているため、デッドロックが発生します。


public class Moo extends MIDlet {

    protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
        // TODO Auto-generated method stub

    }

    protected void pauseApp() {
    }

    protected void startApp() throws MIDletStateChangeException {
        Display display = Display.getDisplay(this);
        MyCanvas myCanvas = new MyCanvas();
        display.setCurrent(myCanvas);
        myCanvas.repaint();

    }

    class MyCanvas extends Canvas {

        protected void paint(Graphics graphics) {
                try {
                        Image bgImage = Image.createImage(getWidth(), getHeight());

                        HttpConnection httpConnection = (HttpConnection) Connector
                                        .open("http://stackoverflow.com/content/img/so/logo.png");
                        Image image = Image.createImage(httpConnection
                                        .openInputStream());
                        bgImage.getGraphics().drawImage(image, 0, 0, 0);
                        httpConnection.close();

                        graphics.drawImage(bgImage, 0, 0, 0);
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }

    }

}

ここでシステム スレッドの呼び出しがどのように行われるか (イベント スレッドと通知スレッド)、およびデッドロックにつながる一連のイベントを教えてください。ここでデッドロックにつながるスレッドが何であるかについては明確ではありません。

  1. j2me スレッド モデルに関するドキュメントはありますか?
  2. j2me システム クラスのソースはどこで入手できますか (接続クラスの実装を調べたいのですが)?

編集:上記のコードでは、ロジックを取得します。しかし、以下のコードは少なくとも正しく動作するはずですか? これは、別のスレッドでネットワーク接続を行っている場所でもデッドロックします。


public class Foo extends MIDlet {

    protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
        // TODO Auto-generated method stub
    }

    protected void pauseApp() {
        // TODO Auto-generated method stub
    }

    protected void startApp() throws MIDletStateChangeException {
        Display display = Display.getDisplay(this);
        MyCanvas myCanvas = new MyCanvas();
        display.setCurrent(myCanvas);
        myCanvas.repaint();
    }

    class MyCanvas extends Canvas {
        protected void paint(Graphics graphics) {
            try {
                Image bgImage = Image.createImage(getWidth(), getHeight());

                FetchImage fetchImage = new FetchImage();
                Thread thread = new Thread(fetchImage);
                thread.start();

                thread.join();

                bgImage.getGraphics().drawImage(fetchImage.image, 0, 0, 0);

                graphics.drawImage(bgImage, 0, 0, 0);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public class FetchImage implements Runnable {
        public Image image;

        public void run() {
            HttpConnection httpConnection;
            try {
                httpConnection = (HttpConnection) Connector
                        .open("http://10.4.71.200/stage/images/front/car.png");
                image = Image.createImage(httpConnection.openInputStream());
                httpConnection.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }
}

4

4 に答える 4

9

j2me システム クラスのソースはどこで入手できますか (接続クラスの実装を調べたいのですが)?

できません。実際にはベンダーに依存します。ノキアがこの状況に対処する方法は、モトローラとは異なる場合があります。

あなたが学ばなければならない教訓は、システムが応答しなくなる可能性があるため、システム コールバックで高価な計算を行わないことです。そのため、時間のかかる操作を別のスレッドに配置し、コールバックからできるだけ早く戻るようにしてください。

2 番目の例では別のスレッドを作成しましたが、paint() でその完了を待ち、最終的にはまったくスレッド化されない結果になります!

あなたができることの1つは、

class MyCanvas extends Canvas {

    Image image;
    boolean imageFetchFailed;

    protected void paint(Graphics g) {
        if (image == null) {
            fetchImage();
            g.drawString("Fetching...", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP)

        } else if (imageFetchFailed) {
            g.drawString("Failed to fetch image", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP)
        } else {
            g.drawImage(image, 0, 0, 0);
        }
    }


    private void fetchImage() {
        new Thread(new Runnable() {
            public void run() {
                HttpConnection httpConnection = null;
                try {
                    httpConnection = (HttpConnection) Connector
                            .open("http://10.4.71.200/stage/images/front/car.png");
                    image = Image.createImage(httpConnection.openInputStream());
                } catch (IOException e) {
                    e.printStackTrace();
                    imageFetchFailed = true;
                }

                if (httpConnection != null) {
                    try {
                        httpConnection.close();
                    } catch (IOException ignored) {
                    }
                }

                // Following will trigger a paint call 
                // and this time image wont be null and will get painted on screen
                repaint();    
            }
        }).start();
    }
}
于 2009-05-15T12:56:08.027 に答える
1

Canvas.paint() はイベント配信メソッドです。つまり、システム イベント スレッドによって呼び出されます。

システム上で、Canvas.paint() 呼び出しとユーザー確認イベント処理の両方が UI イベント スレッド (UT) によって実行されるように実装されているとします。

ここで、Connector.open() によって Canvas.paint() 内で UT がブロックされると、UT は次に来るイベント (この場合は Connector.open() によってトリガーされるユーザー確認イベント) を処理できません。アプリケーション コード内でブロックされている場合、UT が別のイベントを処理することはできません。

そのため、ここでデッドロックが発生し、接続スレッドは何も起こらないのを待っており、UT を永久にブロックします。

一般に、システム イベント スレッドがどのように実装されるかを期待するべきではなく、できるだけ早くイベント処理メソッドから戻るようにしてください。そうしないと、パフォーマンスが低下したり、このようなデッドロックが発生したりする可能性があります。

于 2009-05-15T11:40:52.607 に答える
1

基本的な問題は、一部の Java VM 実装が同じ Java スレッドを使用してすべてを実行することです。

VM のスレッド モデルについて最初に理解する必要があることの 1 つは、誰がそれを開発したかです。

ここに J2ME ライセンシーのリストがあります: http://java.sun.com/javame/licensees/index.jsp

その情報から、VM が使用しているネイティブ スレッドの数を把握してみてください。通常の 2 つのモデルは、すべてのバイトコード解釈を 1 つのネイティブ スレッドで実行するか、各 Java スレッドを独自のネイティブ スレッドで実行するかのいずれかです。

次のステップは、基盤となるオペレーティング システム API の非同期性に関する情報を収集することです。VM を開発するとき、ライセンシーはネイティブ コードを記述して VM をオペレーティング システムに移植する必要がありました。プロセス間通信または低速伝送媒体 (フラッシュ カードから GPS 信号まで) の使用は、システムが一部のデータを待機している間、バイトコード インタープリター スレッドを実行し続けることができる別のネイティブ スレッドを使用して実装される場合があります。

次のステップは、VM の実装がいかに不十分であるかを理解することです。通常、問題は、VM が MIDP 仕様のすべてのコールバック メソッドに対して 1 つの内部 Java スレッドのみを使用する場合に発生します。そのため、間違った Java スレッドで接続を開こうとすると、接続が開かれるまでキーパッド イベントに反応する機会がありません。

さらに悪いことに、たとえば Canvas.paint() が javax.microedition.media.PlayerListener.playerUpdate() と同じ Java スレッドで呼び出されるため、実際には画面が更新されないようにすることができます。

VM 実装の観点からは、標準 API 呼び出しのブロック解除を使用するのと同じ Java スレッドから、制御しないコールバック (リスナーのような「ユーザー」コードになる可能性があるため) を呼び出すことはできないというのが黄金律です。そこにある多くの VM は単にその規則を破っているため、Sun は JavaME 開発者がそれを回避することを推奨しています。

于 2009-05-15T10:49:44.650 に答える
0

いくつかの良いアイデアですが、Manoj の例には競合状態があるようです。

イメージのダウンロード中に複数のペイント呼び出しが可能であり、複数のスレッドが作成され、すべて同じイメージがダウンロードされます (余分なペイント呼び出しの一例は、HTTP 接続プロンプトがポップアップしたときです)。

すべてのペイント呼び出しは同じスレッドで行われるため、ペイント呼び出し内でフラグをテストして設定することにより、同期を回避できます。以下は、改良版の試みです。

    class MyCanvas extends Canvas {

    Image image;
    boolean imageDownloadStarted;
    boolean imageFetchFailed;

    protected void paint(Graphics g) {
        g.fillRect(0, 0, g.getClipWidth(), g.getClipHeight());
        if (image == null) {
            if (imageDownloadStarted)
                return;
            imageDownloadStarted = true;
            fetchImage();
            g.drawString("Fetching...", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP);

        } else if (imageFetchFailed) {
            g.drawString("Failed to fetch image", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP);
        } else {
            g.drawImage(image, 0, 0, 0);
        }
    }

    private void fetchImage() {
        new Thread(new Runnable() {

            public void run() {
                try {
                    final HttpConnection httpConnection = (HttpConnection) Connector.open("http://stackoverflow.com/content/img/so/logo.png");
                    try {
                        final InputStream stream = httpConnection.openInputStream();
                        try {
                            image = Image.createImage(stream);
                        } finally {
                            stream.close();
                        }
                    } finally {
                        httpConnection.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    imageFetchFailed = true;
                }

                repaint();
            }
        }).start();
    }
}

null テストを回避するためのfinalキーワードの使用と、openInputStreamによって返されるストリームの明示的な終了に注意してください。

于 2009-06-20T19:14:43.157 に答える