j2me网络,线程和死锁

时间:2009-05-15 09:01:26

标签: java multithreading java-me mobile deadlock

下面(摘录之后)死锁的简单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类的实现)?
  3. 编辑:在上面的代码中,我得到了逻辑。但是下面的代码应该至少可行吗?这个也在我在一个单独的线程中进行网络连接的地方死锁。

    
    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 个答案:

答案 0 :(得分:9)

  

我在哪里可以获得资源   j2me系统类(我想查看   out实现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();
    }
}

答案 1 :(得分:1)

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

您需要了解VM的线程模型的第一件事就是谁开发了它。

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

根据该信息,尝试确定您的VM使用了多少本机线程。 2个通常的模型要么将所有字节码解释运行到一个本机线程中,要么将每个java线程运行到它自己的本机线程中。

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

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

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

从VM实现的角度来看,黄金法则是你无法控制的任何回调(因为它最终可能以“用户”代码结束,如监听器)无法从你使用unblock标准的同一个java线程中调用API调用。很多虚拟机只是违反了这条规则,因此Sun建议JavaME开发人员解决这个问题。

答案 2 :(得分:1)

Canvas.paint()是event delivery method,这意味着它由系统事件线程调用。

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

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

这就是死锁发生的原因here,连接线程正在等待永远不会发生的事情,并永远阻止UT。

通常,您不应该期望如何实现系统事件线程,并尽可能快地从事件处理方法返回。否则,您可能会收到较低的性能或死锁。

答案 3 :(得分: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 返回的流的显式关闭。