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 系统类的源代码(我想查看 Connection 类的实现)?

编辑:在上面的代码中,我得到了逻辑。但是下面的代码至少应该可以正常工作吗?这也导致我在一个单独的线程中进行网络连接的死锁。


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 系统类的源代码(我想查看 Connection 类的实现)?

你不能。它实际上取决于供应商。诺基亚处理这种情况的方式可能与摩托罗拉不同。

您必须吸取的教训是不要在系统回调中进行昂贵的计算,因为这可能会使系统无响应。因此,将耗时的操作放在单独的线程中,并始终尽早从回调中返回。

即使您在第二个示例中创建了一个单独的线程,您仍等待它在 paint() 中完成,它最终会导致根本没有线程!

你可以做的一件事是

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

嗯,基本问题是一些 Java VM 实现使用相同的 Java 线程来做所有事情。

关于 VM 的线程模型,您需要弄清楚的第一件事就是谁开发了它。

这里有一个 J2ME 被许可人列表:http: //java.sun.com/javame/licensees/index.jsp

根据这些信息,尝试找出您的 VM 正在使用多少本机线程。两种常见的模型要么将所有字节码解释运行到一个本地线程中,要么将每个 java 线程运行到它自己的本地线程中。

下一步是收集有关底层操作系统 API 的异步程度的信息。在开发 VM 时,被许可方必须编写本机代码才能将 VM 移植到操作系统。任何进程间通信或使用慢速传输介质(从闪存卡到 GPS 信号)都可以使用单独的本机线程来实现,该线程可以允许字节码解释器线程在系统等待某些数据时继续运行。

下一步是意识到虚拟机的实现有多糟糕。通常,当 VM 对 MIDP 规范中的所有回调方法仅使用一个内部 java 线程时,就会出现问题。因此,如果您尝试在错误的 java 线程中打开连接,则只有在打开连接之后,您才有机会对键盘事件做出反应。

更糟糕的是,您实际上可以阻止刷新屏幕,因为 Canvas.paint() 将在与 javax.microedition.media.PlayerListener.playerUpdate() 相同的 Java 线程中被调用。

从 VM 实现的角度来看,黄金法则是任何您无法控制的回调(因为它可能以“用户”代码结束,如侦听器)不能从您使用解除阻塞标准 API 调用的同一个 java 线程调用。那里的许多 VM 只是违反了该规则,因此 Sun 建议 JavaME 开发人员解决它。

于 2009-05-15T10:49:44.650 回答
1

Canvas.paint() 是一种事件传递方法,这意味着它被系统事件线程调用。

假设在系统上,Canvas.paint() 调用和用户确认事件处理均由 UI 事件线程 (UT) 实现。

现在,当 UT 在 Canvas.paint() 中被 Connector.open() 阻塞时,UT 肯定无法处理下一个即将到来的事件,在这种情况下是由 Connector.open() 触发的用户确认事件。当另一个事件在您的应用程序代码中被阻塞时,UT 无法处理它。

这就是为什么在这里发生死锁,连接线程正在等待永远不会发生的事情,并永远阻塞UT。

一般来说,你不应该期望系统事件线程将如何实现,并尽可能快地从事件处理方法中返回。否则,您可能会收到较低的性能或像这样的死锁。

于 2009-05-15T11:40:52.607 回答
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();
    }
}

请注意使用final关键字来避免空测试和openInputStream返回的流的显式关闭。

于 2009-06-20T19:14:43.157 回答