1

Я работаю над графическим приложением Java. На каком-либо событии (например, нажатие кнопки) я хочу прочитать большое количество данных из файла в новом рабочем потоке и сохранить его в общей переменной. Затем из этого рабочего потока я хочу вызвать EDT (Event Dispatching Thread) и использовать данные.Последовательное использование общей переменной в Java

Нет одновременного доступа к данным - просто создайте данные в одном потоке и по завершении используйте его в другом потоке.

Я не уверен, что при вызове EDT из рабочего потока убедитесь, что EDT увидит обновленные данные или, если возможно, что EDT будет работать только с какой-то старой версией.

Я не хочу вдаваться в использование изменчивых полей и синхронизированных коллекций из-за сложной структуры моего класса данных и потому, что я не хочу делать свой код медленнее, используя ненужную синхронизацию.

псевдокод пример:

EDT: MyType data; 

EDT: start new Worker thread; 
Worker: loadDataFromFileInNewThread(data); 
Worker: invokeEDT; //using java.awt.EventQueue.invokeLater 
EDT: use data; 

Является ли это использование разделяемой памяти в порядке или я должен заставить EDT использовать обновленную версию так или иначе?

+0

Жаль, что это неправильно документировано. Я ожидаю, что он будет работать нормально. Но, конечно, вы могли бы просто обновить изменчивую переменную или AtomicReference.Чтение изменчивой переменной не окажет существенного влияния на производительность приложения. Чтение большого файла: как на луну и вернуться. Чтение AtomicReference: как мигание глаз. –

+0

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

+1

FYI, invokeLater doesn 't_start_ EDT. EDT уже запущен, ожидая событий. Это должно быть так, потому что ваша программа уже нажала кнопку. Когда вы хотите, чтобы EDT что-то сделал, вы _post событие_. Метод invokeLater() публикует событие общего назначения, не относящееся к GUI, которое в основном означает _do this ..._. –

ответ

2

В рабочей нити, вызывающей java.awt.EventQueue.invokeLater, в конечном счете блокируется java.awt.EventQueue.pushPopLock перед тем, как поставить ваш Runnable. Это означает, что ваш рабочий синхронизируется на этой блокировке в этот момент.

На стороне EDT, EDT находится в цикле, вызывающем java.awt.EventQueue.getNextEvent, для приема и обработки работы. getNextEvent замки java.awt.EventQueue.pushPopLock перед тем, как удалить ваш Runnable.

На этом этапе мы знаем, что как рабочий, так и EDT синхронизированы на одном объекте, что создает точку сброса памяти между двумя потоками, в соответствии с JMM.

Таким образом, в вашем конкретном случае вам не нужна дополнительная синхронизация или volatile, чтобы гарантировать, что EDT будет получать самую свежую информацию, созданную вашим рабочим потоком, до тех пор, пока рабочий поток перестанет изменять общие данные после позвонив по телефону invokeLater (это было бы лучшей практикой).

Элемент реализации: Старые версии Java не используют java.util.concurrent.locks.Lock, но используют метод метода метода EventQueue. Тем не менее, те же самые правила применяются, как описано выше, с помощью Lock.

+0

Мне просто интересно: применимо ли это к workThread1-> employeeThread2 или только к работнику-> EDT? – Abdul

0

Самый простой способ заключается в использовании SwingWorker для асинхронной задачи загрузки, поскольку это упрощает синхронизацию с EDT и обеспечивает крючки для отправки периодически обновляет на EDT (например, для отчетности о ходе):

// should only be accessed by the EDT 
MyData myData; 

class MyDataLoder extends SwingWorker<MyData, Object> { 
    @Override 
    public MyData doInBackground() { 
     // this method runs in a background thread 
     // do loading of data here 
     // update progress periodically: setProgress(...); 

     return loadedData; 
    } 

    @Override 
    protected void done() { 
     // called after doInBackground completes 
     // runs in EDT 
     try { 
      // publish your data to the EDT 
      myData = get(); 
     } catch (Exception ignore) { } 
    } 
} 

As вы можете видеть, данные передаются в EDT в методе done(). Этот метод вызывается в EDT, и там данные безопасно «вытягиваются» из фонового потока в EDT через get(), поэтому он безопасно публикуется в EDT.

Как хороший побочный эффект, вы можете привязать некоторый индикатор выполнения к свойству progress объекта SwingWorker - чтобы вы могли отображать ход загрузки.

+0

Я считал SwingWorker, но это не гонка - мне нужно, чтобы задача была повторяемой, а создание нового SwingWorkers ограничено 10, плюс законченный SwingWorker просто зависает в течение 10 минут, пока он не будет удален (регулярный поток кажется лучшим вариантом в большинстве случаев) – Abdul

+0

@Abdul я понимаю. Никогда не слышал об этом 10-минутном выпуске. Но я думаю, что вы все еще можете взять основную идею - сделать фоновый поток, выполняющий работу, лучше всего сделать через «ExecutorService», и опубликовать результат через EDT. Таким образом, вам придется сделать свой собственный 'invokeLater (this :: done)' - неважно. – isnot2bad