同样的计算结果如何产生不同的结果

问题描述:

只需花费两个小时来调试这个问题,我想我知道问题的原因,但我不明白它怎么能产生它所做的那种结果。同样的计算结果如何产生不同的结果

我有一个基于Swing应用程序,我重写JPanel.paint(图形G)这样

public void paint(Graphics g) { 
    <snipped code> 

    Insets is = getInsets(); 
    Dimension sz = getSize(); 
    g2.setClip(is.left + 1, is.top + 1, sz.width - is.right - is.left - 2, sz.height - is.top - is.bottom - 2); 

    int w = getWidth(); 
    int h = getHeight(); 
    double s = Math.min(sz.getWidth(), sz.getHeight()); 

    AffineTransform mc = g2.getTransform(); 

    AffineTransform m1 = new AffineTransform(mc); 
    m1.preConcatenate(AffineTransform.getTranslateInstance(-m_CenterX, -m_CenterY)); 
    m1.preConcatenate(AffineTransform.getScaleInstance(m_ScaleX * s, -m_ScaleY * s)); 
    m1.preConcatenate(AffineTransform.getTranslateInstance(w/2, h/2)); 

    AffineTransform m2 = new AffineTransform(mc); 
    m2.preConcatenate(AffineTransform.getTranslateInstance(-m_CenterX, -m_CenterY)); 
    m2.preConcatenate(AffineTransform.getScaleInstance(m_ScaleX * s, -m_ScaleY * s)); 
    m2.preConcatenate(AffineTransform.getTranslateInstance(w/2, h/2)); 

    try { 
     m_InverseViewTransform = m1.createInverse(); 
    } catch (NoninvertibleTransformException e) { 

     AffineTransform m3 = new AffineTransform(mc); 
     m3.preConcatenate(AffineTransform.getTranslateInstance(-m_CenterX, -m_CenterY)); 
     m3.preConcatenate(AffineTransform.getScaleInstance(m_ScaleX * s, -m_ScaleY * s)); 
     m3.preConcatenate(AffineTransform.getTranslateInstance(w/2, h/2)); 

     System.out.println("m1 = " + m1); 
     System.out.println("m2 = " + m2); 
     System.out.println("m3 = " + m3); 

     AffineTransform m4 = new AffineTransform(mc); 
     System.out.println("m4 = " + m4); 
     m4.preConcatenate(AffineTransform.getTranslateInstance(-m_CenterX, -m_CenterY)); 
     System.out.println("m4 = " + m4); 
     m4.preConcatenate(AffineTransform.getScaleInstance(m_ScaleX * s, -m_ScaleY * s)); 
     System.out.println("m4 = " + m4); 
     m4.preConcatenate(AffineTransform.getTranslateInstance(w/2, h/2)); 
     System.out.println("m4 = " + m4); 

     System.out.println(w); 
     System.out.println(h); 
     System.out.println(s); 
     System.out.println(m_CenterX); 
     System.out.println(m_CenterY); 
     System.out.println(m_ScaleX); 
     System.out.println(m_ScaleY); 

     e.printStackTrace(); 

现在的奥秘,有时当我启动应用程序,往往不是,说三次在四个中,引发NoninvertibleTransformException。

正如你所看到的调试,我输出违规矩阵和三个其他相同计算的矩阵和用于形成这些矩阵的变量。

这就是它变得有趣的地方,请看下面的输出,所有的矩阵都没有相同的值!

m1 = AffineTransform[[0.0, 0.0, 400.0], [0.0, -0.0, 289.0]] 
m2 = AffineTransform[[0.0, 0.0, 400.0], [0.0, -0.0, 289.0]] 
m3 = AffineTransform[[0.0, 0.0, 400.0], [0.0, -0.0, 289.0]] 
m4 = AffineTransform[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] 
m4 = AffineTransform[[1.0, 0.0, -49.5], [0.0, 1.0, -0.362762953421903]] 
m4 = AffineTransform[[5.254545454545455, 0.0, -260.1], [0.0, -587.9922126928986, 213.3017916655554]] 
m4 = AffineTransform[[5.254545454545455, 0.0, 139.89999999999998], [0.0, -587.9922126928986, 502.3017916655554]] 
800 
578 
578.0 
49.5 
0.36276295342190257 
0.009090909090909092 
1.0172875652126274 
java.awt.geom.NoninvertibleTransformException: Determinant is 0 
at java.awt.geom.AffineTransform.createInverse(AffineTransform.java:2706) 

更有趣的东西,如果我适当地移动调试输出/计算周围深入挖掘这个问题消失。

这种情况只发生在jdk1.8.0_25.jdk(Mac OS X 10.8.5)的JRE中,而不是Java 1.6或1.6。

好吧,我认为真正的罪魁祸首是JPanel不是在Event Dispatched线程中创建的,一旦我这样做了,那么异常永远不会抛出,没有不一致。

所以很明显是一个线程相关的时间依赖问题,但我很好奇在Java JVM的“规则”中如何发生这种情况?所涉及的所有变量都是本地方法,并且AffineTransform的矩阵方法不应该有副作用很难理解螺纹问题如何导致这样的事情...

对于我的想法,我想了解这里发生了什么。

作为一个副作用,这个问题可能会帮助一些可怜的灵魂在类似的问题上挣扎。

另外我不介意如果有人设法在代码/调试/扣除中指出如此明显的缺陷,因为我现在已经盯着这些线路几个小时......。

+0

那么,你的值(我看到像m_CenterX这样的东西)在除了读取它们的EDT之外的任何其他线程上更改(或初始化)?如果是这样,那么你知道你可能有一个多线程问题。 – NESPowerGlove 2014-11-04 16:29:42

+0

难道是另一个线程正在与您从中复制初始转换的'g2'同时'混淆'? - 嗯。可能不会,看到你正在为你的调试输出使用相同的'mc'。 – JimmyB 2014-11-04 16:36:02

+0

感谢和公平点,m_CenterX等在MouseListener中更新。但是,这是运行在EDT ...也许当我在主线程(而不是EDT,因为它应该是)创建JPanel时,paint()被调用错误的线程(至少一次)。嗯,我可以在出现问题时输出线程名称...。 – nyholku 2014-11-04 16:39:37

如果该方法不依赖任何共享数据,那么它不能表现出您描述的行为。但是,从您提供的内容中可以看出,您的方法避免了共享数据。我特别怀疑变量m_CenterX,m_CenterY,m_ScaleXm_ScaleY。这些闻起来像你的类的实例变量,如果是这样的话,它们当然是在实例化类和EDT的线程之间共享的。如果没有正确的同步,EDT可能会在构造函数(或其他线程中调用的其他方法)分配值之前查看这些变量的默认值。更一般地说,通过EDT中运行的方法直接或间接访问的类的任何实例变量都在EDT和创建对象的线程(如果这些不同)之间共享。

+0

对,它们是成员变量,但由于paint和事件处理程序访问这些m_ *变量应该在EDT中调用(在Swing/AWT线程模型中),这应该不是问题,但是如果(由于缺陷实例化JPanel在EDT)也许他们不是......我需要验证这一点。 – nyholku 2014-11-04 19:29:05

+0

如果EDT通过在不同线程中运行的构造函数或方法进行初始化(没有适当的同步),那么EDT就会读取这些实例变量的值。甚至有这样的同步问题的术语:“不正确的发布”。在这个特定的例子中,EDT可以在构造器完成初始化之前看到对象的状态。 – 2014-11-04 19:40:03

+0

John,好的指针,搜索“不正确的发布”提供了许多关于对象如何可见但未被完全/正确初始化的答案。如果你在答案中提到这个问题(不介意你说了一下但不是必要的),我会接受这个答案。 – nyholku 2014-11-05 09:41:07