tzv*_*k15 2 java swing multithreading
我正在开发一个测量由汽车对象驱动的“距离”的应用程序。每辆车都是它自己的线程,具有随机生成的速度,我计算每辆车行驶的距离。虽然我能够以我想要的任何时间间隔(目前一次/秒,但可能稍后更新)从线程内获取正确的数据打印到控制台,但我正在尝试找出一种方法来显示该信息一个 Swing GUI(或者作为表格行,或者只是作为文本区域行)由于 Car 类是一个单独的文件,我不能将其直接推送到 GUI(或者至少,我不知道该怎么做那)。我需要定期更新两“列”信息:当前速度和距离。
我尝试做的事情:设置一个 jtable 并提取行数据:这使我能够捕获快照,但不能不断更新数据。
将信息推送到 jtextarea:我可以将其作为快照来执行。当我尝试将其包装在 while 循环中(线程运行时,追加...)时,系统崩溃了。当我尝试将追加包装在 Thread.sleep 中时,结果相同。
由于在任何给定时间我都可以拍摄汽车位置的快照(car.getLocation()),所以我想的是,也许主要方法可以每秒主动寻找该快照,但是,当我用 while 循环尝试时和 Thread.sleep,正如前面提到的,它使系统崩溃。
另外值得注意的是,完成后,GUI 将允许创建任意数量的 Car 对象,并且我希望每个对象都有一行,并定期更新,以便可以比较距离数字。
编辑:根据@matt的建议,我添加了一个摆动计时器并修改了GUI以适应。现在的挑战是,当我调整页面大小时,新的 jtextfield 仅会在显示屏上弹出。有没有办法以某种方式更新/刷新 GUI?
更新的 GUI 元素:
JButton carb = new JButton("add car");
        GridBagConstraints carC = new GridBagConstraints();
        carC.gridx = 1;
        carC.gridy = 2;
        carb.addActionListener(a -> {
            totalCars++;
            String carName = "Car" + totalCars;
            Car car = new Car(carName);
            cars.add(car);
            Thread thread = new Thread(car);
            JTextField t = new JTextField(50);
            GridBagConstraints tC = new GridBagConstraints();
            t.setEditable(false);
            tC.gridx = 1;
            tC.gridy = currentcol;
            currentcol++;
            content.add(t, tC);
            running = true;
            thread.start();
            ActionListener taskPerformer = new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    t.setText(df.format(car.getLocation()));
                    t.setText("Name: " + carName + ", Drive speed: " + car.getSpeed() + ", Current speed: "
                            + car.getCurrentSpeed() + ", Location (distance from beginning): "
                            + df.format(car.getLocation()));
                }
            };
            Timer timer = new Timer(1000, taskPerformer);
            timer.start();
        });
        content.add(carb, carC);
代码(到目前为止): 汽车类别:
      private void setLocation(long timeElapsed) {
        location = location + ((timeElapsed-lastCheck)*(currentSpeed*0.44704));
        lastCheck = timeElapsed;
    }  
      public void run() {
        long startTime = System.currentTimeMillis();
        long elapsedTime;
        while (flag) {
            try {
                Thread.sleep(1000);
                elapsedTime = System.currentTimeMillis()-startTime;
                elapsedTime = elapsedTime/1000;
                setLocation(elapsedTime);
                System.out.println(getName()+": " + df.format(getLocation()) + " meters");
            } catch (InterruptedException e) {
            }
        }
    }
这是我尝试在 main 方法中使用 Thread.sleep 的方法。这不起作用(导致系统崩溃):
            while (running) {
                try {
                    Thread.sleep(1000);
                    carArea.append("\nName: " + carName + ", Drive speed: " + car.getSpeed() + ", Current speed: "
                            + car.getCurrentSpeed() + ", Location (distance from beginning): " + car.getLocation());
                } catch (InterruptedException e) {
                }
            }
首先,首先看一下Swing 中的并发性。重要的是,Swing 不是线程安全的,您永远不应该从事件调度线程的上下文之外更新 UI(或它所依赖的状态)
Timer这为您提供了一种定期安排重复回调的方法,该回调在事件调度线程的上下文中执行
SwingWorker这为您提供了一个包装类,它可以在后台线程上执行功能,但可用于将信息传递回事件调度线程以进行“处理”
EventQueue.invokeLater这基本上提供了在事件调度线程上执行功能的途径(通过a Runnable interface)。虽然这很有用,但很难将信息从一个上下文传递到另一个上下文而无需构建它,这应该让您SwingWorker查看
您需要问的下一个问题是,您想要“推送”更新还是“轮询”更新。这将影响您用于更新 UI 的 API 方法。
例如,如果你想轮询状态,SwingTimer是一个不错的选择。可以在您需要时执行更新。如果有大量更新,这将是一个不错的解决方案,因为您不太可能使 EDT 过载,而这可能会导致其他问题。
相反,如果您想推送更新(从汽车上,可能通过某种观察者),那么SwingWorker“可以”工作,但似乎有很多开销,只需使用EventQueue.invokeLater. 如果汽车实际上没有自己的线程支持,那么 aSwingWorker将非常有用,但它的行为或多或少会像 Swing 一样Timer。
但是,如果您只想知道汽车何时跨过某种阈值(即又行驶了 10 公里),那么我可能会想使用 aSwingWorker来轮询汽车的状态,并在触发阈值时将更新推送到通过publish/工作流程的 UI process。
话虽如此,SwingWorker仅限 10 名活跃员工,所以这是您需要考虑的一个因素。
Timer)此示例通过 Swing 使用“轮询”方法,Timer这样做的好处是,汽车的顺序不会改变(由 UI 控制)。虽然您可以通过多种方法来控制它JTable,但这非常简单。
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;
public class Main {
    public static void main(String[] args) {
        new Main();
    }
    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
    public class TestPane extends JPanel {
        private List<Car> cars = new ArrayList<>(32);
        private JTextArea ta = new JTextArea(10, 20);
        private Random rnd = new Random();
        private NumberFormat format = NumberFormat.getNumberInstance();
        public TestPane() {
            for (int index = 0; index < 32; index++) {
                Car car = new Car("Car " + index, 40.0 + (rnd.nextDouble() * 180.0));
                cars.add(car);
            }
            setLayout(new BorderLayout());
            add(new JScrollPane(ta));
            Timer timer = new Timer(1000, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    for (Car car : cars) {
                        ta.append(car.getName() + " @ " + format.format(car.getSpeedKPH()) + " ~ " + format.format(car.getLocation()) + "km\n");
                        if (!car.isRunning() && rnd.nextBoolean()) {
                            car.start();
                            ta.append(car.getName() + " got started\n");
                        }
                    }
                }
            });
            timer.start();
        }
    }
    public class Car {
        private String name;
        private double speedKPH;
        private Instant timeStartedAt;
        public Car(String name, double kmp) {
            this.speedKPH = kmp;
            this.name = name;
        }
        public String getName() {
            return name;
        }
        public double getSpeedKPH() {
            return speedKPH;
        }
        public Instant getTimeStartedAt() {
            return timeStartedAt;
        }
        public boolean isRunning() {
            return timeStartedAt != null;
        }
        public void start() {
            timeStartedAt = Instant.now();
        }
        protected double distanceTravelledByMillis(long millis) {
            double time = millis / 1000d / 60d / 60d;
            return getSpeedKPH() * time;
        }
        public double getLocation() {
            Instant timeStartedAt = getTimeStartedAt();
            if (timeStartedAt == null) {
                return 0;
            }
            Duration time = Duration.between(timeStartedAt, Instant.now());
            return distanceTravelledByMillis(time.toMillis());            
        }
    }
}
您还可以控制更新的速度,这将允许您在发现问题之前将解决方案扩展到更多数量的汽车(数万辆)(尽管JTextArea在此之前将是瓶颈)
EventQueue.invokeLater)这是一个更随机的示例,每辆车都有自己的更新间隔,这会通过观察者触发回调。然后,观察者需要将回调同步回事件调度队列,然后才能将更新添加到文本区域。
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.text.NumberFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class Main {
    public static void main(String[] args) {
        new Main();
    }
    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
    public class TestPane extends JPanel {
        private List<Car> cars = new ArrayList<>(32);
        private JTextArea ta = new JTextArea(10, 20);
        private Random rnd = new Random();
        private NumberFormat format = NumberFormat.getNumberInstance();
        public TestPane() {
            for (int index = 0; index < 32; index++) {
                int timeInterval = 500 + rnd.nextInt(4500);
                Car car = new Car("Car " + index, 40.0 + (rnd.nextDouble() * 180.0), timeInterval, new Car.Observer() {
                    @Override
                    public void didChangeCar(Car car) {
                        updateCar(car);
                    }
                });
                cars.add(car);
                car.start();
            }
            setLayout(new BorderLayout());
            add(new JScrollPane(ta));
        }
        protected void updateCar(Car car) {
            if (!EventQueue.isDispatchThread()) {
                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        updateCar(car);
                    }
                });
            }
            ta.append(car.getName() + " @ " + format.format(car.getSpeedKPH()) + " ~ " + format.format(car.getLocation()) + "km\n");
        }
    }
    public class Car {
        public interface Observer {
            public void didChangeCar(Car car);
        }
        private String name;
        private double speedKPH;
        private Instant timeStartedAt;
        private int notifyInterval;
        private Observer observer;
        private Thread thread;
        public Car(String name, double kmp, int notifyInterval, Observer observer) {
            this.speedKPH = kmp;
            this.name = name;
            this.notifyInterval = notifyInterval;
            this.observer = observer;
        }
        public String getName() {
            return name;
        }
        public double getSpeedKPH() {
            return speedKPH;
        }
        public Instant getTimeStartedAt() {
            return timeStartedAt;
        }
        public boolean isRunning() {
            return timeStartedAt != null;
        }
        public void start() {
            if (thread != null) {
                return;
            }
            timeStartedAt = Instant.now();
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(notifyInterval);
                        observer.didChangeCar(Car.this);
                    } catch (InterruptedException ex) {
                    }
                }
            });
            thread.start();
        }
        protected double distanceTravelledByMillis(long millis) {
            double time = millis / 1000d / 60d / 60d;
            return getSpeedKPH() * time;
        }
        public double getLocation() {
            Instant timeStartedAt = getTimeStartedAt();
            if (timeStartedAt == null) {
                return 0;
            }
            Duration time = Duration.between(timeStartedAt, Instant.now());
            return distanceTravelledByMillis(time.toMillis());
        }
    }
}
这会给 EDT 带来大量开销,因为每辆车都需要在 EDT 上添加要处理的请求,因此添加的车辆越多,EDT 的速度就会开始减慢。
| 归档时间: | 
 | 
| 查看次数: | 360 次 | 
| 最近记录: |