Как один и тот же расчет может дать разные результаты

Просто потратьте два часа на отладку этого, я думаю, что знаю причину проблемы, но я не понимаю, как она может давать такие результаты.

У меня есть приложение на основе Swing, в котором я переопределяю JPanel.paint(Graphics 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)

Более интересные вещи, если я соответствующим образом перемещаю вывод/вычисления отладки, чтобы углубиться в это, проблема исчезает.

Это происходит только с JRE из jdk1.8.0_25.jdk (в Mac OS X 10.8.5), а не с Java 1.6 или 1.6.

Хорошо, я думаю, что настоящий виновник в том, что JPanel не был создан в потоке отправки событий, как только я сделал это, исключение никогда не выбрасывается, несоответствий нет.

Совершенно очевидно, что проблема, связанная с потоком, зависит от времени, но мне любопытно, как это может произойти с «правилами» Java JVM, все задействованные переменные являются локальными для метода, а матричные методы из AffineTransform не должны иметь побочных эффектов, так что добрый трудно понять, как проблемы с потоками могут вызвать что-то подобное…

Для моего разума я хотел бы понять, что здесь происходит.

В качестве побочного эффекта этот вопрос может помочь какой-нибудь бедняге, борющейся с подобными проблемами.

Также я не возражаю, если кому-то удастся указать такой очевидный недостаток в коде/отладке/выводах, поскольку я уже часами смотрю на эти строки….


person nyholku    schedule 04.11.2014    source источник
comment
Ну, ваши значения (я вижу такие вещи, как m_CenterX) изменены (или инициализированы) в каком-либо другом потоке, кроме EDT, который их считывает? Если это так, то вы знаете, что у вас, вероятно, проблема с многопоточностью.   -  person NESPowerGlove    schedule 04.11.2014
comment
Может ли быть так, что другой поток одновременно «возится» с g2, из которого вы копируете начальное преобразование? -- Хм. Вероятно, нет, учитывая, что вы используете один и тот же mc для вывода отладки.   -  person JimmyB    schedule 04.11.2014
comment
Спасибо и честно говоря, m_CenterX и т. д. обновляются в MouseListener. Но это выполняется в EDT… возможно, когда я создаю JPanel в основном потоке (а не в EDT, как это должно быть), paint() вызывается в неправильном потоке (по крайней мере, один раз). Хммм, я мог бы вывести название темы, когда возникает проблема….   -  person nyholku    schedule 04.11.2014
comment
@Hanno Я не думаю, что это имеет значение, поскольку я получаю копию преобразования только один раз.   -  person nyholku    schedule 04.11.2014


Ответы (1)


Если бы метод не полагался на какие-либо общие данные, он не мог бы демонстрировать описанное вами поведение. Однако из того, что вы представили, не ясно, что ваш метод избегает общих данных. Я особенно подозрительно отношусь к переменным m_CenterX, m_CenterY, m_ScaleX и m_ScaleY. Они пахнут переменными экземпляра вашего класса, и если это так, то они, безусловно, совместно используются потоком, создающим экземпляр класса, и EDT. При отсутствии надлежащей синхронизации EDT может видеть значения этих переменных по умолчанию до того, как им будут присвоены значения конструктором (или другими методами, вызванными в другом потоке).

В более общем смысле любые переменные экземпляра вашего класса, к которым прямо или косвенно обращаются методы, работающие в EDT, совместно используются EDT и потоком, в котором создается ваш объект (если они отличаются).

person John Bollinger    schedule 04.11.2014
comment
Верно, они являются переменными-членами, но поскольку paint и обработчики событий, которые обращаются к этим переменным m_*, должны вызываться в EDT (в потоковой модели Swing/AWT), это не должно быть проблемой, однако, если (из-за сбоя при создании экземпляра JPanel в EDT), возможно, это не так… Мне нужно это проверить. - person nyholku; 04.11.2014
comment
Для EDT возникает проблема при чтении значений этих переменных экземпляра, если они инициализируются с помощью конструктора или метода, который выполняется в другом потоке (без соответствующей синхронизации). Существует даже термин для такого рода проблем с синхронизацией: неправильная публикация. В этом конкретном примере EDT может видеть состояние объекта до того, как конструктор завершит его инициализацию. - person John Bollinger; 04.11.2014
comment
Джон, хороший указатель, поиск неправильной публикации дает ряд ответов SO о том, как объект может быть видимым, но не инициализированным полностью/правильно. Если вы упомянете это в ответе (не возражаете, если вы немного уточните, но не обязательно), я приму это как ответ. - person nyholku; 05.11.2014