7

我有一个带有单个类的早期 Java Web Start 应用程序。它在 Windows 和 Linux 上运行,但在 Mac OS X 上会出现可怕的 Invalid Thread Access 错误。我意识到这已经在其他地方处理过。我花了整整两天时间在互联网上搜索并实施了所有解决方案,但问题仍然存在。

我的理解是对 SWT 的调用必须从主线程进行,这里就是这种情况。如果我错了,请纠正我。

我将在下面发布 3 个片段,应用程序的源代码、jnlp 文件的相关部分以及 Mac 上的错误消息。问题在最后。


JAVA源代码

package client;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class AccountWindow {
 public static void main(String[] args) {
  Display display = new Display(); **// error occurs here**
  Shell shell = new Shell(display); shell.open();
  while (!shell.isDisposed()) {
   if (!display.readAndDispatch())
    display.sleep();
  }
  display.dispose();
 }
}

JNLP 片段

<resources os="Mac\ OS\ X" arch="x86_64">
    <j2se version="1.5+" java-vm-args="-XstartOnFirstThread" />
    <nativelib href="swt-4.2-cocoa-macosx-x86_64.jar" />
</resources>

错误信息

org.eclipse.swt.SWTException: Invalid thread access
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.widgets.Display.error(Unknown Source)
    at org.eclipse.swt.widgets.Display.createDisplay(Unknown Source)
    at org.eclipse.swt.widgets.Display.create(Unknown Source)
    at org.eclipse.swt.graphics.Device.<init>(Unknown Source)
    at org.eclipse.swt.widgets.Display.<init>(Unknown Source)
    at org.eclipse.swt.widgets.Display.<init>(Unknown Source)
    at client.AccountWindow.main(AccountWindow.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.sun.javaws.Launcher.executeApplication(Launcher.java:1550)
    at com.sun.javaws.Launcher.executeMainClass(Launcher.java:1488)
    at com.sun.javaws.Launcher.doLaunchApp(Launcher.java:1299)
    at com.sun.javaws.Launcher.run(Launcher.java:114)
    at java.lang.Thread.run(Thread.java:637)

请注意- http://www.eclipse.org/swt/faq.php#javawebstart
上 发布的 display.syncExec 解决方案不适用,因为在调用它之前,您需要一个显示。当我尝试创建显示时,会发生这里的错误。 - 我已经使用 JaNeLa 来验证 jnlp 文件并且没有红色错误。 - <resources os="Mac\ OS\ X" arch="i386"> 被正确解释,因为正在加载正确的 swt 库。- 您可以在http://thelinkjuicer.com/gannonline/client.jnlp 重现错误




现在的问题
任何人都可以在源代码或 jnlp 片段中看到任何会导致错误的内容吗?
第二个问题:如何判断 -XstartOnFirstThread 参数是否实际上被 VM 读取?

4

2 回答 2

6

显然,您的 main 方法没有在主线程上执行。您可以在堆栈跟踪中看到启动器实际上是在另一个线程中启动的,然后Launcher才间接调用main. 不幸的是,这只是诊断,我不确定解决方案。我做过类似的事情(通过 Java Web Start 的 SWT 应用程序),但我不记得我们是如何解决这个问题的,如果有的话。

在检查了com.sun.javaws.Launcher源代码之后,还不清楚它是如何工作的。该Launcher.launch方法启动一个新线程,在该线程中main执行您的方法。您可以按照代码重新创建您获得的确切堆栈跟踪。

Java Web Start的主入口点显示主线程在启动后很快就死掉了。

更新

我挖出了一些东西:在这个 Eclipse 错误报告中,建议问题可能与此有关:

<resources>
  <j2se version="1.4+" />
  <jar href="client.jar" />
</resources>

解析器从这里获取 j2se 规范并忽略后面更具体的规范。尝试删除<j2se...线。

更新 2

现在我从这里挖出来:

com.apple.concurrent.Dispatch.getInstance().getNonBlockingMainQueueExecutor().execute(
  new Runnable() { public void run() {
      final Display display = Display.getDefault(); 
      while (!display.isDisposed()) {
        if (!display.readAndDispatch())
          display.sleep();
      }
});

这实际上听起来像是可行的。它完全按照我在下面的评论中所描述的:通过专门为此目的而设置的机制将补丁插入主线程。尝试根据您的需要进行调整。您甚至可能不需要 -XstartOnFirstThread 。

更新 3

我终于找到了我的旧 SWT-JWS 项目。里面有这个:

<resources os="Mac OS X" arch="x86_64">
  <j2se version="1.6+" java-vm-args="-XstartOnFirstThread"/>
  <jar href="swt-cocoa-macosx-x86-64-3.6.2.jar" />
</resources>

它有效。它没有默认j2se元素,该元素出现在 OSX 特定条目中。

于 2012-10-17T15:54:59.960 回答
3

This is an answer to the secondary question, "How can you tell if the -XstartOnFirstThread argument is actually being read by the VM?" (or the related question, "how can you detect if -XstartOnFirstThread was passed to the VM?") I looked at java.lang.management.RuntimeMXBean.getInputArguments(), but -XstartOnFirstThread is not included in the returned List. After some research, I was able to figure out something, so I hope this helps someone else who was in my shoes.

According to this link, there are several environment variables set by the launcher. Among them are:

JAVA_MAIN_CLASS_pid
JAVA_STARTED_ON_FIRST_THREAD_pid

Use System.getenv() to obtain a Map of the environment variables. From there, you can iterate through the entrySet() until you find an Entry with a getKey() whose return value starts with "JAVA_MAIN_CLASS_" . If the discovered Entry's getValue() contains the name of your main class, you can use the rest of the key to determine the pid.

Once you have the pid, look up the string "JAVA_STARTED_ON_FIRST_THREAD_pid" in the environment Map. If it exists and has the value "1", the process was started with -XstartOnFirstThread. Otherwise, the process was started without the flag.

This probably won't work in an unsigned WebStart application, since the System.getenv() method is prohibited by default. But in a signed webstart application, or in a regular Java application, this does work.

Hope that helps,

于 2013-03-05T16:39:29.103 回答