How can same calculation produce different results

2019-08-08 09:01发布

问题:

Just spend two hours debugging this, I think I know the cause of the problem but I don't understand how can it produce the kind of results it does.

I've got a Swing based application where I override JPanel.paint(Graphics g) like this

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

Now the mystery, sometimes when I launch the application ,more often than not, say three times out of four, the NoninvertibleTransformException is thrown.

As you can see to debug I output the offending matrix and three other identically calculated matrixes and the variables that are used to form those matrixes.

And this is where it gets interesting, see the output below, all the matrixes don't have the same value!

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)

More interesting stuff, if I suitably move the debug output/calculations around to dig deep into this the problem disappears.

This happens only with JRE from jdk1.8.0_25.jdk (on Mac OS X 10.8.5), not with Java 1.6 or 1.6.

Ok, I think the real culprit is that the JPanel was not created in the Event Dispatched thread, once I did that the exception is never throw there are no inconsistencies.

So pretty obviously a thread related timing dependent problem but I'm curious how can this happen with in Java JVM 'rules' all the variable involved are local to the method and the the matrix methods from AffineTransform are not supposed to have side effects so kind of hard to understand how thread issues can cause something like this…

For my piece of mind I would like to understand what is going on here.

As a side effect this question may help some poor soul struggling with similar issues.

Also I don't mind if someone manages to pin point so obvious flaw in the code/debug/deductions as I've been staring at those lines now for hours….

回答1:

If the method did not rely on any shared data then it could not exhibit the behavior you describe. It is not clear from what you presented, however, that your method avoids shared data. I am particularly suspicious of variables m_CenterX, m_CenterY, m_ScaleX, and m_ScaleY. These smell like instance variables of your class, and if so then they are certainly shared between the thread that instantiates the class and the EDT. Absent proper synchronization, the EDT may then see the default values of those variables, before they are assigned values by the constructor (or other methods invoked in another thread).

More generally, any instance variables of your class that are accessed directly or indirectly by methods running in the EDT are shared between the EDT and the thread in which your object is created (if those are different).