线程类方法运行内部无限循环



我有一个用线程运行的小动画。我用两个按钮暂停并播放动画。下面是run方法中的代码:

public void run(){
while(true){
if(!exitFlag)
randomBall();
}
}

当我第一次点击播放时,我启动线程并且动画运行,但是当我点击暂停并再次点击播放时,动画不工作。有一件奇怪的事情发生了,当我添加System.out.println("here")时,程序运行正常。

public void run(){
while(true){
System.out.println("here");
if(!exitFlag)
randomBall();
}
}
我不知道我在这里做错了什么。这是我的完整代码。
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Button;
import java.awt.Color;
import java.util.Random;
public class Main extends Applet implements Runnable{
Button play;
Button pause;
int xpos;
int ypos;
boolean right;
int randomYInc; //random x position increment
int randomXInc; //random y position increment
boolean top;
boolean firstTime = true;
boolean exitFlag = false;
Thread th;
Random rand;
public void init(){
play = new Button("play");
pause = new Button("pause");
MyHandler mh = new MyHandler();
play.addActionListener(mh);
pause.addActionListener(mh);
add(play);
add(pause);
th = new Thread(this);
xpos = 0;
ypos = 50;
right = true;
rand = new Random();
randomize();
}
public void randomize(){
randomYInc = rand.nextInt((20 - 10) + 10) + 10;
randomXInc = rand.nextInt((20 - 10) + 10) + 10;
}
private class MyHandler implements ActionListener{
public void actionPerformed(ActionEvent e){
if(e.getSource() == play){
if(firstTime){
th.start();
firstTime = false;
}else{
exitFlag = false;    
}
}else if(e.getSource() == pause){
exitFlag = true;
//try{th.wait();}catch(Exception ex){}
}
}
}
public void paint(Graphics g){
g.setColor(Color.blue);
g.fillOval(xpos, ypos, 30, 30);
}
public void run(){
while(true){
if(!exitFlag)
randomBall();
}
}
public void randomBall(){
if(right && xpos < 270){ //did not hit right border yet
if(xpos + randomXInc > 270){
xpos = 270;
}else{
xpos += randomXInc;
}
if(ypos > 0 && top){ //did not hit top border yet
if(ypos - randomYInc < 0){
ypos = 0;
}else{
ypos -= randomYInc;
}
}
if(ypos < 270 && !top){ //did not hit bottom border yet
if(ypos + randomYInc > 270){
ypos = 270;
}else{
ypos += randomYInc;
}
}
}
if(!right && xpos > 0){ //did not hit left border yet
if(xpos - randomXInc < 0){
xpos = 0;
}else{
xpos -= randomXInc;
}
if(ypos > 0 && top){ //did not hit top border yet
if(ypos - randomYInc < 0){
ypos = 0;
}else{
ypos -= randomYInc;
}
}
if(ypos < 270 && !top){ //did not hit bottom border yet
if(ypos + randomYInc > 270){
ypos = 270;
}else{
ypos += randomYInc;
}
}
}
//conditions to change ball directions when it hits a border
if(xpos <= 0){
right = true;
top = true;
randomize();
}else if(xpos >= 270){
right = false;
top = false;
randomize();
}else if(ypos <= 0){
right = true;
top = false;
randomize();
}else if(ypos >= 270){
right = false;
top = true;
randomize();
}
repaint();
try{
Thread.sleep(80);
}catch(Exception ex){}
}
}

您没有显示所有的代码,但我认为exitFlag没有标记为volatile

简而言之,没有volatile,线程可以缓存变量的值:没有某种内存屏障,它看不到另一个线程设置的新值。

尝试使用AtomicBoolean代替booleanvolatile boolean

如果将exitFlag设置为false,则循环简化为:while(true) {}

换句话说,一个所谓的自旋循环:CPU 100%完全投入,很快你的笔记本电脑就会飞出窗外,风扇以全功率旋转,将CPU的热气从排气口排出。然后5分钟后你的笔记本电脑死机了,因为它没电了。

不要spinloop。

使用Thread.sleepwait或锁定某些东西,让CPU知道它不必无休止地重新评估true是否仍然为真。或者,直接关闭线程,如果用户再次点击start,启动一个新线程,可能更简单。

听起来你可能有同步性问题,以及EDT问题,以及事件循环问题。你还没有粘贴足够的内容来确定。

  • synchronicity:所有线程都有一个恶币。从某种意义上说,它不会同样频繁地击中正面/反面,相反,它只会让你恼火,最大化地应用墨菲定律:每当写入字段时,硬币就会从任何地方翻转。如果它是头,则只对本地副本进行写操作,仅对该线程是唯一的,其他线程不会看到您的更改。如果是尾部,所有其他线程都会看到它。实际上,它更邪恶:On表示硬币也被翻转了。这个游戏的名字就是让VM永远不会翻转它。要做到这一点,你必须建立"事前发生"的关系。关于如何做到这一点,已经有整本书都在写了。这里最简单的方法,对你来说?不要使用布尔值。使用AtomicBoolean的实例,并在其上调用.set(true)。(如果你对我提到的那些书感兴趣,可以搜索"Java内存模型")。

  • EDT:你不能做GUI的东西,除非在事件调度线程。你新启动的运行randomBall()的线程不是。使用SwingUtilities。invoklater,或swingworkers。不清楚你在这里使用的是什么UI框架,但大多数人都有"GUI交互仅在EDT上"的想法。当您注册事件处理程序时,这些处理程序在EDT中运行,因此响应"此按钮被单击"的代码可能会扰乱UI。但不是像你在这里写的帖子。

  • 事件循环:通常你不希望线程只是不停地工作,并在CPU允许的情况下尽可能快地完成工作。这将耗尽其他线程和资源。相反,你可以设定一些节奏,比如60fps,并锁定它。在任何情况下,在系统上运行的所有线程和事物中,它们要么都应该是速率限制的,要么除了一个之外都应该是速率限制的,并且几乎总是需要"所有都是速率限制的"部分。这意味着Thread.sleepobj.wait必须参与其中,或者一些在底层使用这些东西的框架。如果不这样做,就会导致所说的饥饿,这很容易解释你所观察到的现象。

避免自旋循环的另一种方法是使用信号量

那么你可以有

Semaphore sem = new Semaphore(1, true);

你已经变成了一个循环。

while(true){
sem.acquire();
sem.release();
randomBall();
}

然后在您的操作监听器中,stop方法使用sem.acquire(),这将使while循环在获取时停止,因为只有一个许可。start方法将使用sem.release().

最新更新