Иногда зависает база sqlite при выполнении запроса

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


    public static ResultSet UseUserScript(File fileEntry)
    {
        send(new Script(fileEntry).getCode());

        return results.remove(Thread.currentThread());
    }

    private static void send(String script)
    {
        MarkedStatement st = new MarkedStatement();
        st.script = script;
        st.Thread = Thread.currentThread();
        queue.add(st);
        synchronized (IOThread)
        {
            IOThread.notify();
        }

        synchronized (Thread.currentThread())
        {
            try {
                Thread.currentThread().wait();//Зависание обычно происходит в этой строке
            }
            catch (InterruptedException ex)
            {
                ex.printStackTrace();
            }

        }
        return;
    }
    @Override
    public void run() {
        while (isOnline)
        {

            if (queue.size() > 0)
            {
                try {
                    MarkedStatement st = queue.remove();
                    Statement statement = connection.createStatement();
                    statement.execute(st.script);

                    results.put(st.Thread, statement.getResultSet());
                    synchronized (st.Thread)
                    {
                        st.Thread.notify();
                    }
                }
                catch (SQLException ex)
                {
                    ex.printStackTrace();
                }

            }
            else
            {
                synchronized (Thread.currentThread())
                {
                    try {
                        Thread.currentThread().wait();
                    }
                    catch (InterruptedException ex)
                    {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }

Ответы (1 шт):

Автор решения: Roman Konoval

Проблема возникает из-за неправильного использования wait/notify.

В данной программе возможно такое выполнение, при котором notify будет послан, когда когда поток получатель уведомления, еще не выполнил wait, а значит после выполнения wait поток будет ждать вечно, т.к. новое уведомление никто больше не пошлет.

Главная проблема в программе это то, что сначала происходит добавление MarkedStatement в очередь, и только потом поток начинает ждать уведомления.

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

Например, если непосредственно перед этим блоком синхронизации:

synchronized (Thread.currentThread())
{
     try {
         Thread.currentThread().wait();//Зависание обычно происходит в этой строке
     } ...

произойдет переключения контекста и управление получит поток, в котором выполняется run. Если за время до следующего переключения контекста этот поток успеет выполнить запрос и послать уведомление с помощью notify, то первый поток, никогда это уведомление не получит.

Чтобы этого не случилось, добавлять задачу в очередь и посылать об этом уведомление, нужно в блоке синхронизации, который делает wait. Это даст гарантию, что поток посылающий notify не сможет это сделать до тех пор пока поток ждущий уведомление не вызовет wait (т.к. для вызова notify нужно захватить монитор, а он не будет доступен, пока не произойдет вызов wait, который собственно и отпускает монитор).

Вообще, wait/notify сложно использовать правильно именно из того, что нужно делать точную синхронизацию времени посылки и приема уведомления. Гораздо проще и надежнее использовать более высокоуровневые примитивы синхронизации.

В этом случае хорошо подошел бы CountDownLatch. Его первое (и главное в этом конкретном случае) преимущество, что для он помнит состояние, и не важно когда посылающий поток сделал уведомление. Даже если оно было послано до того, как получатель начал слушать, получатель все равно сможет узнать было ли уведомление:

private static void send(String script)
    {
        MarkedStatement st = new MarkedStatement();
        st.script = script;
        st.resultReadySignal = new CountDownLatch(1);
        queue.add(st);
        synchronized (IOThread)
        {
            IOThread.notify();
        }

        st.resultReadySignal.await();
        return;
    }
    @Override
    public void run() {
        while (isOnline)
        {

            if (queue.size() > 0)
            {
                try {
                    MarkedStatement st = queue.remove();
                    Statement statement = connection.createStatement();
                    statement.execute(st.script);
                    results.put(st.Thread, statement.getResultSet());
                    st.resultReadySignal.countDown();
                }
                catch (SQLException ex)
                {
                    ex.printStackTrace();
                }

            }
            else
            {
                synchronized (Thread.currentThread())
                {
                    try {
                        Thread.currentThread().wait();
                    }
                    catch (InterruptedException ex)
                    {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }

Еще одно замечание. В этом примере я только изменил, то что касается конкретно этой проблемы. Еще могут быть похожие сложности з уведомлением которое посылается, чтоб рабочий поток начал обработку задачи из очереди. Тут опять же лучше использовать более высокоуровневый класс для очереди BlockingQueue.

→ Ссылка