【问题标题】:j2me networking, threads and deadlocksj2me 网络、线程和死锁
【发布时间】:2009-05-15 09:01:25
【问题描述】:

下面的一段简单的 midlet 代码(Moo 类)(在摘录之后)死锁(至少我认为在阅读线程 here 上的这篇文章后它会死锁)。

我已经转载了帖子的相关摘录:

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 类的实现)?

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

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

【问题讨论】:

    标签: java multithreading java-me mobile deadlock


    【解决方案1】:

    我在哪里可以找到资源 j2me 系统类(我想检查 出连接的执行 类)?

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

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

    即使您在第二个示例中创建了一个单独的线程,您仍会在 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();
        }
    }
    

    【讨论】:

    • 很好的问答,特别是如果你真的想清楚地了解这个线程。
    【解决方案2】:

    Canvas.paint() 是一个event delivery method,表示它被系统事件线程调用。

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

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

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

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

    【讨论】:

    • 谢谢。我想我现在有点明白了。您将如何重新排列代码以使其正常工作?我看到的他们的方式是在从网络获取图像后让系统调用paint方法。有更好的方法吗?是否有任何与我面临的问题相关的模式和标准解决方案?抱歉,我完全是 J2ME 和 UI 方面的新手。
    • Manoj 刚刚向您展示了一个很好的示例,请注意 thread.join() 仍然在 Canvas.paint() 中阻塞了 UT。尽量从事件回调中尽快返回,让事件线程处理下一个事件。
    【解决方案3】:

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

    首先要弄清楚 VM 的线程模型是谁开发的。

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

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

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

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

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

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

    【讨论】:

    • 谢谢。根据您的回复更新了问题。
    【解决方案4】:

    一些好主意,但在 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 返回的流。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-10
      • 2018-06-16
      • 2016-04-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多