java – 没有print语句,循环看不到其他线程更改的值
在我的代码中,我有一个循环,等待从不同的线程更改某个状态.另一个线程工作,但我的循环永远不会看到更改的值.它永远等待.但是,当我在循环中放入一个System.out.println语句时,它突然起作用!为什么?
以下是我的代码示例: class MyHouse { boolean pizzaArrived = false; void eatPizza() { while (pizzaArrived == false) { //System.out.println("waiting"); } System.out.println("That was delicious!"); } void deliverPizza() { pizzaArrived = true; } } 当while循环运行时,我从另一个线程调用deliverPizza()来设置pizzaArrived变量.但循环仅在我取消注释System.out.println(“等待”)时才有效;声明.这是怎么回事? 解决方法
允许JVM假设其他线程在循环期间不更改pizzaArrived变量.换句话说,它可以在循环外提升pizzaArrived == false测试,优化:
while (pizzaArrived == false) {} 进入这个: if (pizzaArrived == false) while (true) {} 这是一个无限循环. 要确保一个线程所做的更改对其他线程可见,您必须始终在线程之间添加一些同步.最简单的方法是使共享变量volatile: volatile boolean pizzaArrived = false; 使变量volatile变得可以保证不同的线程可以看到彼此变化对它的影响.这可以防止JVM缓存pizzaArrived的值或在循环外提升测试.相反,它必须每次都读取实变量的值. (更正式地说,volatile会在访问变量之间创建一个先发生的关系.这意味着在发送披萨之前all other work a thread did对于接收披萨的线程也是可见的,即使这些其他更改不是针对volatile变量的.) Synchronized methods主要用于实现互斥(防止两件事同时发生),但它们也具有与volatile相同的副作用.在读取和写入变量时使用它们是另一种使更改对其他线程可见的方法: class MyHouse { boolean pizzaArrived = false; void eatPizza() { while (getPizzaArrived() == false) {} System.out.println("That was delicious!"); } synchronized boolean getPizzaArrived() { return pizzaArrived; } synchronized void deliverPizza() { pizzaArrived = true; } } 打印声明的效果 System.out是一个PrintStream对象. PrintStream的方法是这样同步的: public void println(String x) { synchronized (this) { print(x); newLine(); } } 同步可防止在循环期间缓存pizzaArrived.严格地说,两个线程必须在同一个对象上同步,以保证对变量的更改是可见的. (例如,在设置pizzaArrived之后调用println并在读取pizzaArrived之前再次调用它将是正确的.)如果只有一个线程在特定对象上同步,则允许JVM忽略它.在实践中,JVM不够智能,无法证明其他线程在设置pizzaArrived后不会调用println,所以它假设它们可能.因此,如果调用System.out.println,则无法在循环期间缓存变量.这就是为什么这样的循环在有打印语句时起作用的原因,尽管它不是正确的解决方法. 使用System.out并不是导致这种效果的唯一方法,但它是人们最常发现的,当他们试图调试为什么他们的循环不起作用时! 更大的问题 while(pizzaArrived == false){}是一个忙等待循环.那很糟!当它等待时,它会占用CPU,从而减慢其他应用程序的速度,并增加系统的功耗,温度和风扇速度.理想情况下,我们希望循环线程在等待时休眠,因此它不会占用CPU. 以下是一些方法: 使用wait / notify 一个低级解决方案是use the wait/notify methods of class MyHouse { boolean pizzaArrived = false; void eatPizza() { synchronized (this) { while (!pizzaArrived) { try { this.wait(); } catch (InterruptedException e) {} } } System.out.println("That was delicious!"); } void deliverPizza() { synchronized (this) { pizzaArrived = true; this.notifyAll(); } } } 在这个版本的代码中,循环线程调用 在对象上调用wait / notify时,必须保持该对象的同步锁,这就是上面的代码所做的.你可以使用你喜欢的任何对象,只要两个线程都使用相同的对象:这里我使用了这个(MyHouse的实例).通常,两个线程不能同时进入同一对象的同步块(这是同步目的的一部分),但它在这里工作,因为线程在wait()方法内部时临时释放同步锁. BlockingQueue的
class MyHouse { final BlockingQueue<Object> queue = new LinkedBlockingQueue<>(); void eatFood() throws InterruptedException { // take next item from the queue (sleeps while waiting) Object food = queue.take(); // and do something with it System.out.println("Eating: " + food); } void deliverPizza() throws InterruptedException { // in producer threads,we push items on to the queue. // if there is space in the queue we can return immediately; // the consumer thread(s) will get to it later queue.put("A delicious pizza"); } } 注意:BlockingQueue的put和take方法可以抛出InterruptedExceptions,这是必须处理的已检查异常.在上面的代码中,为简单起见,重新抛出了异常.您可能更喜欢捕获方法中的异常并重试put或take调用以确保它成功.除了这一点之外,BlockingQueue非常容易使用. 这里不需要其他同步,因为BlockingQueue确保在将项目放入队列之前所做的所有线程对于将这些项目取出的线程是可见的. 执行人 执行程序就像现成的BlockingQueues执行任务一样.例: // A "SingleThreadExecutor" has one work thread and an unlimited queue ExecutorService executor = Executors.newSingleThreadExecutor(); Runnable eatPizza = () -> { System.out.println("Eating a delicious pizza"); }; Runnable cleanUp = () -> { System.out.println("Cleaning up the house"); }; // we submit tasks which will be executed on the work thread executor.execute(eatPizza); executor.execute(cleanUp); // we continue immediately without needing to wait for the tasks to finish 有关详细信息,请参阅 事件处理 在等待用户在UI中单击某些内容时循环是错误的.而是使用UI工具包的事件处理功能. In Swing,例如: JLabel label = new JLabel(); JButton button = new JButton("Click me"); button.addActionListener((ActionEvent e) -> { // This event listener is run when the button is clicked. // We don't need to loop while waiting. label.setText("Button was clicked"); }); 因为事件处理程序在事件派发线程上运行,所以在事件处理程序中执行长时间的工作会阻止与UI的其他交互,直到工作完成.可以在新线程上启动慢速操作,或使用上述技术之一(wait / notify,BlockingQueue或Executor)将调度分派给等待的线程.您还可以使用专为此设计的 JLabel label = new JLabel(); JButton button = new JButton("Calculate answer"); // Add a click listener for the button button.addActionListener((ActionEvent e) -> { // Defines MyWorker as a SwingWorker whose result type is String: class MyWorker extends SwingWorker<String,Void> { @Override public String doInBackground() throws Exception { // This method is called on a background thread. // You can do long work here without blocking the UI. // This is just an example: Thread.sleep(5000); return "Answer is 42"; } @Override protected void done() { // This method is called on the Swing thread once the work is done String result; try { result = get(); } catch (Exception e) { throw new RuntimeException(e); } label.setText(result); // will display "Answer is 42" } } // Start the worker new MyWorker().execute(); }); 计时器 要执行定期操作,可以使用 Timer timer = new Timer(); TimerTask task = new TimerTask() { @Override public void run() { System.out.println(System.currentTimeMillis()); } }; timer.scheduleAtFixedRate(task,1000); 每个java.util.Timer都有自己的后台线程,用于执行其调度的TimerTasks.当然,线程在任务之间休眠,因此它不会占用CPU. 在Swing代码中,还有一个类似的 JFrame frame = new JFrame(); frame.setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE); Timer timer = new Timer(1000,(ActionEvent e) -> { frame.setTitle(String.valueOf(System.currentTimeMillis())); }); timer.setRepeats(true); timer.start(); frame.setVisible(true); 其他方法 如果您正在编写多线程代码,那么值得探索这些包中的类以查看可用的内容: > java.util.concurrent 另请参阅Java教程的Concurrency section.多线程很复杂,但有很多帮助可用! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |