下面(摘录之后)死锁的简单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();
}
}
}
}
有人可以告诉我如何在这里完成系统线程调用(事件和通知线程)以及导致死锁的事件序列。我不清楚这里涉及到导致死锁的线程是什么。
编辑:在上面的代码中,我得到了逻辑。但是下面的代码应该至少可行吗?这个也在我在一个单独的线程中进行网络连接的地方死锁。
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();
}
}
}
}
答案 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 返回的流的显式关闭。