#java #multithreading #swing
#java #многопоточность #swing
Вопрос:
Я сделал минимальное представление моей полной программы, чтобы показать, с чем мне нужна помощь, программа состоит из JFrame, основного класса и класса задач.
Класс task генерирует случайные целые числа, которые используются для отображения строк в графическом интерфейсе. Я сделал так, чтобы пользователь мог использовать любое количество потоков, каждый из которых выполняет одну и ту же задачу.
Моя цель — отобразить конечное «решение», которое на данный момент не является реальным решением. Но, поскольку каждый поток генерирует целочисленные значения, я хочу постоянно обновлять графический интерфейс, чтобы он мог продолжать показывать решения, пока не достигнет окончательного «истинного» решения.
Как я могу это сделать?
Когда я пытаюсь передать графический интерфейс через него, он обновляется только один раз. Я пытался изучить Swing Workers, но я хочу использовать свою собственную реализацию потоков, и я не могу понять, как ее правильно реализовать или как именно это будет работать в параллельной реализации.
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Task extends Thread {
private static Lock taskLock = new ReentrantLock();
private Random rand = new Random();
private static int y;
static volatile ArrayList<Integer> finallist = new ArrayList<>();
private GUI g;
Task(GUI g) {
this.g = g;
}
private void generatePoint() {
taskLock.lock();
y = rand.nextInt(1000);
taskLock.unlock();
}
public void run() {
generatePoint();
int copy = y;
finallist.add(copy);
g.setData(copy);
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
GUI g = new GUI();
g.setVisible(true);
Scanner input = new Scanner(System.in);
System.out.println("Please input the number of Threads you want to create: ");
int n = input.nextInt();
System.out.println("You selected " n " Threads");
Thread[] threads = new Thread[n];
for (int i = 0; i < threads.length; i ) {
threads[i] = new Task(g);
threads[i].start();
}
for (int j = 0; j < n; j ) {
threads[j].join();
}
for(Integer s: Task.finallist) {
if(s > 30) {
g.setData(s); //in full program will find true solution
}
}
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GUI extends JFrame implements ActionListener {
private boolean check;
private int y;
GUI() {
initialize();
}
private void initialize() {
check = false;
y = 0;
this.setLayout(new FlowLayout());
this.setSize(1000,1000);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@Override
public void actionPerformed(ActionEvent e) {
}
@Override
public void paint(Graphics g) {
super.paint(g);
if(check) {
g.drawString("here",500,y);
}
}
void setData(int y) {
check = true;
this.y = y;
repaint();
}
}
Комментарии:
1. Я бы посоветовал вам начать с рассмотрения параллелизма в Swing , в частности рабочих потоков и SwingWorker
2. «Я пытался изучить Swing Workers, но я хочу использовать свою собственную реализацию потоков» —
SwingWorker
использует aExecutor
для выполнения задач — значит, он параллельный
Ответ №1:
Один из важных аспектов, который вам нужно иметь в виду, заключается в том, что Swing не является потокобезопасным, это означает, что вы не должны обновлять пользовательский интерфейс или обновлять какое-либо значение, от которого зависит пользовательский интерфейс, извне потока диспетчеризации контекстных событий.
Существует несколько способов справиться с этим. SwingUtilities.invokeLater
, но вы действительно должны использовать его только в том случае, если количество раз, когда он будет вызван, невелико или время между ними относительно больше (секунды). Основная причина этого в том, что очень легко перегрузить EDT, что может привести к «зависанию» пользовательского интерфейса.
Другой подход заключается в использовании Swing Timer
, но это действительно полезно только в том случае, если вы хотите регулярно обновлять пользовательский интерфейс (например, анимацию).
Другой подход заключается в использовании SwingWorker
. Это «причудливая» оболочка вокруг a Executor
, которая позволяет вам выполнять работу в фоновом потоке, а затем publish
передавать результаты в EDT, которые затем можно безопасно process
редактировать для пользовательского интерфейса.
Но зачем это делать? Ну, одна из вещей, которая SwingWorker
делает это, если вы Thread
генерируете много данных за небольшой промежуток времени, это втиснет несколько результатов в a List
и уменьшит количество обращений к пользовательскому интерфейсу, помогая поддерживать некоторый уровень производительности.
Однако в вашем случае это, вероятно, просто действительно приятное упражнение, поскольку вы создаете один поток / рабочий для выполнения одного задания и не получаете ни одного рабочего / потока для выполнения всех заданий.
В этом случае я мог бы рассмотреть возможность использования Swing Timer
для проверки модели данных вместо того Thread
, чтобы /worker пытался обновить сам пользовательский интерфейс, таким образом, вы получаете контроль над количеством обновлений, которые происходят в пользовательском интерфейсе.
У всего этого есть свои плюсы и минусы, и вам нужно будет выяснить, какие из них лучше всего подходят для вашего конкретного сценария
SwingWorker
пример
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
public class GUI {
public static void main(String[] args) {
new GUI();
}
public GUI() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane(10));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static interface Consumer {
void setData(int value);
}
public static class WorkerTask extends SwingWorker<Void, Integer> {
private static Lock taskLock = new ReentrantLock();
private Random rand = new Random();
private static int y;
// I'd prefer this was a model object passed into the worker
static volatile ArrayList<Integer> finallist = new ArrayList<>();
private Consumer consumer;
public WorkerTask(Consumer consumer) {
this.consumer = consumer;
}
private int generatePoint() {
taskLock.lock();
int copy = 0;
try {
copy = rand.nextInt(200);
y = copy;
// Array list isn't thread safe ;)
finallist.add(copy);
} finally {
taskLock.unlock();
}
return copy;
}
@Override
protected Void doInBackground() throws Exception {
// And a random delay
// You should now be able to see the text change
Thread.sleep(rand.nextInt(1000));
publish(generatePoint());
return null;
}
@Override
protected void process(List<Integer> chunks) {
if (chunks.isEmpty()) {
return;
}
int last = chunks.get(chunks.size() - 1);
consumer.setData(last);
}
}
public class TestPane extends JPanel {
private boolean check;
private int y;
private CountDownLatch latch;
public TestPane(int count) {
latch = new CountDownLatch(count);
for (int i = 0; i < count; i ) {
WorkerTask task = new WorkerTask(new Consumer() {
@Override
public void setData(int value) {
// This is on the EDT, so it's safe to update the UI with
TestPane.this.setData(value);
}
});
task.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
WorkerTask task = (WorkerTask) evt.getSource();
if (task.getState() == SwingWorker.StateValue.DONE) {
latch.countDown();
}
}
});
task.execute();
}
// This only matters if you want the result to be delivered
// on the EDT. You could get away with using a Thread otherwise
SwingWorker waiter = new SwingWorker<Void, List<Integer>>() {
@Override
protected Void doInBackground() throws Exception {
System.out.println("Waiting");
try {
latch.await();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("All done here, thanks");
publish(WorkerTask.finallist);
return null;
}
@Override
protected void process(List<List<Integer>> chunks) {
if (chunks.isEmpty()) {
return;
}
List<Integer> values = chunks.get(chunks.size() - 1);
completed(values);
}
};
waiter.execute();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (check) {
g2d.drawString("here", 100, y);
}
g2d.dispose();
}
void setData(int y) {
check = true;
System.out.println(y);
this.y = y;
repaint();
}
protected void completed(List<Integer> values) {
// And executed on the EDT
System.out.println("All your value belong to us");
for (int value : WorkerTask.finallist) {
System.out.println(">> " value);
}
}
}
}
Я рекомендую посмотреть:
- Параллелизм в Swing
- Рабочие потоки и SwingWorker
- Исполнители, если вы действительно заинтересованы в понимании того, как
SwingWorker
работают s
Комментарии:
1. Большое вам спасибо! Я не ответил, потому что это много, но я, скорее всего, в конечном итоге использую SwingWorker, чтобы я мог справиться с любыми раздражающими проблемами, и, как вы сказали, это в любом случае более эффективно.
2. Это стоит изучить, так как это становится еще одним инструментом в вашем наборе инструментов
3. Я работал над этим, и я попытался сделать несколько замен для того, что у меня было изначально. Я попытался вместо того, чтобы использовать только одно значение y, использовать x и y и поместить их в hashmap. Если я попытаюсь составить список хэш-карт вместо списка целых чисел, это испортит весь метод обработки. Кажется, что необходимо передать значение int, возможно ли использовать эту конфигурацию с чем-то другим, кроме int?
4. Я не уверен, что использование a
HashMap
было бы лучшим выбором, я мог бы рассмотреть возможность использования ajava.awt.Point
вместо5. О, вау, я даже не знал, что это такое, спасибо
Ответ №2:
Прежде чем я объясню решение вашей проблемы, я хотел бы указать на некоторые проблемы с вашим кодом.
- Вы выполняете только одну операцию в каждом потоке, и она не кажется длительной (
run
метод вызывается только один раз, на случай, если вы не знаете). Я не уверен, просто ли вы добавляете его в качестве заполнителя, или это действительно часть вашего кода. Но если у вас нет длительной операции, стоит ли делать это в отдельном потоке? Подумайте о компромиссах. - Нет необходимости присоединяться к потокам, так как они, вероятно, почти сразу завершатся в вашем коде.
Теперь к решению, в этой документации объясняется, что Swing не является потокобезопасным, но есть способ внести изменения в графический интерфейс. Вы должны использовать этот метод для внесения любых изменений : SwingUtilities.invokeLater(..)
.
public static void main(String[] args) throws InterruptedException {
GUI g = new GUI();
g.setVisible(true);
Scanner input = new Scanner(System.in);
System.out.println("Please input the number of Threads you want to create: ");
int n = input.nextInt();
System.out.println("You selected " n " Threads");
Thread[] threads = new Thread[n];
for (int i = 0; i < threads.length; i ) {
threads[i] = new Task(g);
threads[i].start();
}
for (int j = 0; j < n; j ) {
threads[j].join();
}
for(Integer s: Task.finallist) {
if(s > 30) {
// here you submit a Runnable to the event-dispatcher thread in which the Swing Application runs
SwingUtilities.invokeLater(() -> {
g.setData(s);
});
}
}
}
То же самое следует сделать и в вашем run
методе:
public void run() {
generatePoint();
int copy = y;
finallist.add(copy);
SwingUtilities.invokeLater(() -> {
g.setData(copy);
});
}
Комментарии:
1. Просто будьте осторожны — использование
SwingUtiltiies.invokeLater
like «может» перегрузить EDT2. Да, моя полная программа должна занять больше времени, и я понимаю, что вы имеете в виду, говоря о том, как будет работать invokelater, я ценю помощь!
3. Еще один быстрый вопрос, если кто-нибудь может ответить, есть ли простой способ отложить время между вызовом метода? Я слышал, что Thread.sleep не подходит для использования при работе с потоками в swing. Должен ли я просто использовать какой-то таймер?
4.
javax.swing.Timer
был специально создан для анимации в Swing (в этом случаеThread.sleep
это не очень хороший вариант). Подробнее читайте здесь .