【问题标题】:Returning multiple values from a looping thread without killing it in Java从循环线程返回多个值而不在Java中杀死它
【发布时间】:2015-06-10 22:51:27
【问题描述】:

我正在尝试实现一个在线订购系统-isch 的东西,它将通过 Java 中的 MySQL 工作。 使用 MVC,我正在尝试实现一个循环类,该类将不断检查数据库表中的更改,这些更改将在订单到达时保存。现在,对于我收到的每个订单,我都想将其作为“盒子”显示到 JFrame 中。

为此,我使用了一个可调用线程,该线程循环名为 OrderCheck.java 的类。然后从 Controller 调用此函数,如下所示。

public ArrayList<Order> StartOrderChecking() {
    OrderCheck OCObj = new OrderCheck();
    service = Executors.newFixedThreadPool(1); //We want one thread
    task = service.submit(OCObj);   //Submit the thread
    try {
            orderList = task.get();

        } catch (final InterruptedException ex) {
            ex.printStackTrace();
        } catch (final ExecutionException ex) {
            ex.printStackTrace();
        }
    return orderList;
}

而且线程函数看起来有点像这样……

public ArrayList<Order> call() {
    try {
        while (true) {
            System.err.println("Thread looping");
            ConnectToDB();
            query = "SELECT * FROM mb_orders";
            stmt = con.createStatement();
            rs = stmt.executeQuery(query);
            orderList = new ArrayList<Order>();
            while (rs.next()) {
                int ID = rs.getInt("ID");
                String email = rs.getString("email");
                String order = rs.getString("order");
                String orderinfo = rs.getString("orderinfo");
                int phone = rs.getInt("phone");

                newOrder = new Order(ID,email,order,orderinfo,phone); //We create and Order object
                orderList.add(newOrder); // This is my Arraylist that i want to return back to the view
                return orderList;
            }

            stmt.close();
            con.close();
            Thread.sleep(Time);
        }
    } catch (Exception e) {
    }
    return null;
}

代码简单地循环直到程序终止,每 3 秒我检查一次数据库是否有新订单到达,如果有,则将它们存储在 ArrayList 中,然后返回。 现在我面临的问题是这个实现只会返回一次然后线程就会死掉,我想以某种方式从数据库中获取连续的数据更新到控制器,然后将它发送回视图,以便我可以显示它我的图形用户界面。

感谢任何帮助!

我可以完全理解我的实施是否有问题,并且会接受任何帮助来修复实施。我现在可以看到,问题还在于如何在不停止整个程序的情况下不断地将数据从 Controller 移动到 view。

问候

编辑:添加这个是因为我觉得需要对这个问题进行一些澄清。 我使用的是 MVC 模式,这意味着我有 3 个不同的抽象,M 代表模型抽象,它包含与应用程序相关的所有单独的类,V 代表视图抽象,它只会包含用户的代码可以从用户交互中看到和调用。 C 代表控制器抽象,它从视图中获取来自用户交互的调用,并进一步将它们分发到模型中的正确类。

现在,在我的情况下,我有一个在视图抽象中的 EDT 中创建的 GUI,并且上面的 SQL-Conituous-loop-feth-connection-class-thing 在模型中(当然)。我希望这个线程以某种方式继续循环,同时不断地从 SQL 数据库中获取包含所有相关命令的 ArrayList,然后将它们发送回控制器 OR! (如果可能的话,这听起来更好),直接从循环线程刷新视图抽象中的主 GUI。

所以简单地说: - 我如何实现一个循环线程来更新视图中存在的我的 GUI,以便在主框架中显示相关的新订单。

以下是我创建 GUI 的方式和位置,以及我如何调用控制器来启动线程:

public class View extends JFrame {
. 
. 
.
public View(String UN) {
    setTitle("ExpressFood Admin Panel: " + UN);
    setSize(800, 800);
    setLocationRelativeTo(null); //Centered JFrame...
    setResizable(false);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //Panel Setup
    JPanel mainPanel = new JPanel();
    add(mainPanel);
    mainPanel.setLayout(null);
    JMenuBar mainMenu = new JMenuBar();
    //Build test menu if needed...
    JMenu menu = new JMenu("A Menu");
    mainMenu.add(menu);
    setJMenuBar(mainMenu);
}
.
.
private void StartSim() throws SQLException, Exception { 
//Show the main frame to the user
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            JFrame mainFrame = new View(UN);
            mainFrame.setVisible(true);
        }
    });
orderList = controller.StartOrderChecking(); //Call to Controller for OrderCheck.java
}

以下代码是我如何从控制器调用 OrderCheck.java 类:

public ArrayList<Order> StartOrderChecking() {
    OrderCheck OCObj = new OrderCheck();
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    executor.scheduleWithFixedDelay(OCObj, 1, 3, TimeUnit.SECONDS);

    orderList = OCObj.sendToEDTForUpdate();
    return orderList;
}

【问题讨论】:

  • 哪里有抛出异常?异常将导致线程死亡。唯一可能的问题可能是数据库连接未释放或重新建立。
  • 不抛出异常,程序在其状态下工作,但是每当添加/删除数据库条目时持续更新是程序无法做到的事情
  • 注意线程正在返回,这会破坏while循环。
  • 起初我打算发表一个关于自己工作的尖刻评论,然后我意识到你大部分时间都这样做了,你需要帮助来解决一个特定的、可重现的问题,其原因你甚至知道并且解释,并提供相关代码。好问题。欢迎来到本站!
  • 非常感谢!总的来说,我对编程很陌生,问题尚未解决,但我得到了一些非常有价值的帮助!

标签: java mysql multithreading


【解决方案1】:

请勿退货。您已经知道如何创建线程池并在其中执行线程,因此每次数据库查询返回一个订单时,只需创建一个异步线程来执行该订单。并且仅在程序关闭时退出无限循环。

【讨论】:

  • 不。 GUI 上发生的一切必须在 EDT 上发生。你不能在异步线程中处理这个。
【解决方案2】:

您不需要ExecutorService,您需要SwingWorker。

让我们看看SwingWorker 是如何工作的:

new SwingWorker<Void, Order>() {
    @Override
    protected Void doInBackground() throws Exception {
        //publish a chunk, this will get picked up for processing
        publish(newOrder);
        //we need to return null as the method is Void
        return null;
    }

    @Override
    protected void process(List<Order> chunks) {
        //process new chunks on the EDT
    }

    @Override
    protected void done() {
        //Thread is done, probably a problem
    }
};

因此,您必须了解有关 Swing 的一些事项。它不是线程安全的,因此您只能访问 EDT 上的 Swing 组件。这非常重要。

所以,SwingWorker 所做的是doInBackground 方法在某个后台线程上运行。当这个方法完成时,done 方法在EDT上被调用。当您在 doInBackground 方法中调用 publish 时,发布的对象将传递给 process 方法,该方法再次在 EDT 上调用。

因此,除非您知道自己在做什么,否则您应该使用已有的结构来实现工作线程和 EDT 之间的同步。

所以,在你的例子中:

new SwingWorker<Void, Order>() {
    @Override
    protected Void doInBackground() throws Exception {
        while (true) {
            System.err.println("Thread looping");
            ConnectToDB();
            query = "SELECT * FROM mb_orders";
            stmt = con.createStatement();
            rs = stmt.executeQuery(query);
            while (rs.next()) {
                int ID = rs.getInt("ID");
                String email = rs.getString("email");
                String order = rs.getString("order");
                String orderinfo = rs.getString("orderinfo");
                int phone = rs.getInt("phone");

                final Order newOrder = new Order(ID, email, order, orderinfo, phone);
                publish(newOrder);
            }

            stmt.close();
            con.close();
            Thread.sleep(Time);
        }
        return null;
    }

    @Override
    protected void process(List<Order> chunks) {
        for (Order chunk : chunks) {
            //display chunk
        }
    }

    @Override
    protected void done() {
        //Thread is done, probably a problem
        try {
            get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
};

现在,您需要确保您永远不会有一个空的 catch 块。您必须以某种方式处理异常 - 记录或显示它。

事实上,空的 catch 块是许多软件公司发出官方口头警告的原因。

【讨论】:

  • 我知道这将是一个可能的解决方案,请检查编辑。
【解决方案3】:

让我清楚地了解您要做什么。 创建一个线程来持续查询数据库以更新呈现给某个视图的列表。

我注意到您在 while-loop 内调用了 return,它正在退出该方法。

我建议您使用newSingleThreadExecutor,而不是newFixedThreadPool,以及CopyOnWriteArrayList。

使用get方法检索更新orderList。

private  CopyOnWriteArrayList<Order> orderList = new CopyOnWriteArrayList<>();
ExecutorService service = null;

...

public void StartOrderChecking() {
    OrderCheck OCObj = new OrderCheck();
    if(service.isShutdown()){
        service = Executors.newSingleThreadExecutor();
    }else{
        service.shutdownNow();
        service = Executors.newSingleThreadExecutor();
    }

    service.execute(OCObj);
}

...

public ArrayList<Order> getOrderList() {
    ArrayList rtnList = new ArrayList();
    for(Order order : orderList){
        rtnList.add(order);
    }
    return rtnList;
}

...

private class OrderCheck implements Runnable {

    @Override
    public void run() {
        try {
            while (true) {
                System.err.println("Thread looping");
                ConnectToDB();
                query = "SELECT * FROM mb_orders";
                stmt = con.createStatement();
                rs = stmt.executeQuery(query);
                orderList.clear();
                while (rs.next()) {
                    int ID = rs.getInt("ID");
                    String email = rs.getString("email");
                    String order = rs.getString("order");
                    String orderinfo = rs.getString("orderinfo");
                    int phone = rs.getInt("phone");

                    newOrder = new Order(ID, email, order, orderinfo, phone); //We create and Order object
                    orderList.add(newOrder); // This is my Arraylist that i want to return back to the view
                }

                stmt.close();
                con.close();
                Thread.sleep(Time);
            }
        } catch (Exception e) {
            //Log something
        }
    }

}

只需将OCObj.sendToEDTForUpdate() 替换为getOrderList

public ArrayList<Order> StartOrderChecking() {
    OrderCheck OCObj = new OrderCheck();
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    executor.scheduleWithFixedDelay(OCObj, 1, 3, TimeUnit.SECONDS);

    orderList = controller.getOrderlist();
    return orderList;
}

希望这个帮助,祝你编程好运

【讨论】:

  • 对于 MVC 模式,OrderCheck 将成为“模型”的一部分,StartOrderChecking & getOrderList 将成为“控制器”的一部分
【解决方案4】:

这是我的建议

  1. 创建一个 Runnable,在其中执行 db 查询代码,在获取 Order 后将它们发送到 gui 上显示,显示代码应该在 EDT 中。

    类 MyRunnable 实现 Runnable {

    @Override
    public void run() {
        List<Order> orders = connectToDBAndGetOrders();
        sendToEDTForUpdate(orders);
        return ;
    }
    
    private void sendToEDTForUpdate(final List<Order> orders) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                for(Order order : orders) {
                    //display on gui
                }
            }
        });
    }
    
    private List<Order> connectToDBAndGetOrders() {
        String query;
        Statement stmt;
        ResultSet rs;
        List<Order> orderList;
        Connection con;
        try {
            con = ConnectToDB();
            query = "SELECT * FROM mb_orders";
            stmt = con.createStatement();
            rs = stmt.executeQuery(query);
            orderList = new ArrayList<Order>();
            while (rs.next()) {
                int ID = rs.getInt("ID");
                String email = rs.getString("email");
                String order = rs.getString("order");
                String orderinfo = rs.getString("orderinfo");
                int phone = rs.getInt("phone");
    
                Order newOrder = new Order(ID,email,order,orderinfo,phone); //We create and Order object
                orderList.add(newOrder); // This is my Arraylist that i want to return back to the view
                return orderList;
            }
    
            stmt.close();
            con.close();
        } catch (Exception e) {
            //exception handling
        }
        return null;
    }
    

    }

  2. 将此 Runnable 安排为每 3 秒或根据需要执行一次。请节点有 scheduleAtFixedRate 和 scheduleWithFixedDelay 两种方法,我建议使用 scheduleWithFixedDelay。

    ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    MyRunnable c = new MyRunnable();
    
    executor.scheduleWithFixedDelay(c, 1, 3, TimeUnit.SECONDS);
    

编辑示例完整代码

没有数据库连接。当然,这不是生产就绪代码,只是为了展示我的思考过程,

package thread.executor;

import java.awt.FlowLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class MyFrame extends JFrame {

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MyFrame frame = new MyFrame("Background Fetching");

                Controller controller = new Controller(frame);
                controller.startFetchingOrder();
                frame.setSize(600, 600);

                frame.setVisible(true);
            }
        } );

    }

    public MyFrame(String title) {
        super(title);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        FlowLayout layout = new FlowLayout();
        getContentPane().setLayout(layout);
        getContentPane().add(new JButton("Fixed"));
    }

    public void addNewButton(String text) {
        getContentPane().add(new JButton(text));
        validate();
    }


    static class Controller {

        ScheduledExecutorService executor;
        MyFrame view;

        public Controller(MyFrame view) {
            this.view = view; 
        }

        public void startFetchingOrder() {
            executor = Executors.newScheduledThreadPool(1);
            MyRunnable c = new MyRunnable(this);

            executor.scheduleWithFixedDelay(c, 1, 3, TimeUnit.SECONDS);
        }

        public void stopFetchingOrder() {
            if(executor != null)
                executor.shutdown();
        }

        public void sendToEDTForUpdate(final List<Order> orders) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    for(Order order : orders) {
                        System.out.println(Thread.currentThread().getName()+"  adding new order "+order.getId());
                        view.addNewButton(""+order.getId());
                    }
                }
            });
        }
    }

    static class MyRunnable implements Runnable {

        Controller controller;

        public MyRunnable(Controller controller) {
            this.controller = controller;
        }
        @Override
        public void run() {
            List<Order> orders = connectToDBAndGetOrders();
            controller.sendToEDTForUpdate(orders);
            return ;
        }

        static AtomicInteger counter = new AtomicInteger(0);

        private List<Order> connectToDBAndGetOrders() {
            List<Order> list = new ArrayList<Order>();

            int count = (int)(5 * Math.random());

            for (int i = 0; i < count; i++) {
                Order order = new Order((counter.incrementAndGet()*1000)+i, null, null, null, 0);
                System.out.println(Thread.currentThread().getName()+"  got new order "+order.getId());
                list.add(order);
            }

            System.out.println(Thread.currentThread().getName()+"  returningOrders "+list.size());
            return list;
        }
    }
}

class Order {
    int id;

    public Order() { 
        id = 0;
    }
    public Order(int Id, String email, String order, String orderInfo, int phone) {
        this.id = Id;
    }

    public int getId() {
        return id;
    }
}

【讨论】:

  • 老实说,这是我第一次在 Java 上使用线程,我以前只在 Erlang 上做过。现在我已经阅读了有关 EDT 的信息,并且我已经实现了我的 GUI,以便在 EDT 上调用它。请记住,我有一个 MVC 结构:我不明白的是这将如何解决线程不死的问题,以及如何通过它来更新 GUI。例如,我如何访问模型中的 GUI(从视图类调用)以便进行更改。其次,我在哪里添加时间表?
  • @hassan-nazar : 这将如何解决线程不死的问题 : executor.scheduleWithFixedDelay 将永远执行第一个参数 (MyRunnable) 并在两者之间指定延迟 ( 3 秒)。当您想停止检查数据库以获取新订单时,应该致电executor.shutdown()。 还有我将如何更新 GUI - 可能您希望将控制器传递给 MyRunnable,以便它可以在从 DB 获取订单后请求更新视图。
  • 请检查我的帖子中的其他代码,它包含完整的程序,您可以在您身边运行并根据您的应用程序进行更改。
猜你喜欢
  • 2013-08-04
  • 1970-01-01
  • 2016-02-29
  • 2011-03-30
  • 2018-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多