java 线程中布尔值未更改

zwghvu4y  于 2023-01-11  发布在  Java
关注(0)|答案(2)|浏览(154)

我有一个类MPClient和MultiplayerMatch。MultiplayerMatch在其构造函数中创建了一个MPClient可运行线程。
为了避免数据溢出,我在MultiplayerMatch中设置了一个名为“moved”的布尔值,当玩家移动时,它将变为true。
在updateMatch方法中,如果有任何玩家移动,“moved”将变为true,这允许MPClient输入if语句(在while内)。这样,MPClient仅在游戏发生变化时才向服务器发送数据。
然而,当该标志为真时,在MPClient中该更改不会注册!MPClient仍然“认为”moved等于false,即使在MultiplayerMatch中该标志发生更改之后,因此,没有任何内容发送到服务器...
经过几次测试后,我注意到如果我在调试模式下运行它,因为我有一些断点,所以那个更改会被注册,一切都运行得很好!为什么布尔值更改只在调试模式下“看到”?既然有断点,这是否与应用程序的“运行速度”有关?
下面是代码的重要部分:
MPC客户端:

public class MPClient {
static final int TIME_OUT = 5000;
Client client;
MultiPlayMatch match;

public MPClient(String name, int team, MultiPlayMatch match) {
    this.match = match;
    client = new Client();
    client.start();

    Network.registerPackets(client);
    addListeners();

    try {
        client.connect(TIME_OUT, "127.0.0.1", Network.PORT);
    } catch (IOException e) {
        e.printStackTrace();
        client.stop();
    }

    /*this comment is just to show that here is the place where the login information is sent to the server, instead of showing all the code*/

    PlayerInfo playerInfo = new PlayerInfo();
    Network.UpdatePlayer updatePlayer = new Network.UpdatePlayer();
    updatePlayer.name = name;
    updatePlayer.team = team;

    while(true) {
        if(match.moved) {       //--> this is the variable that is always false
            playerInfo.x = match.getClientPlayerX(team);
            playerInfo.y = match.getClientPlayerY(team);

            updatePlayer.x = playerInfo.x;
            updatePlayer.y = playerInfo.y;
            client.sendTCP(updatePlayer);
            match.moved = false;
        }
    }

}

private void addListeners() {
    client.addListener(new Listener.ThreadedListener(new Listener() {
        @Override
        public void received(Connection connection, Object object) {
            if(object instanceof Network.UpdatePlayer) {
                Network.UpdatePlayer updatePlayer = (Network.UpdatePlayer) object;
                match.setPlayerPosition(updatePlayer.x, updatePlayer.y, updatePlayer.name, updatePlayer.team);
            }
        }
    }));
}
}

多人对战:

public class MultiPlayMatch extends Match {

public boolean moved;

public MultiPlayMatch(){
    super(0);

    Random r = new Random();
    int aux = r.nextInt(2);
    aux = 0;
    if(aux == 0){
        homeTeam = new Team("Benfica", Team.TeamState.Attacking, w);
        visitorTeam = new Team("Porto", Team.TeamState.Defending, w);
    } else{
        homeTeam = new Team("Benfica", Team.TeamState.Defending, w);
        visitorTeam = new Team("Porto", Team.TeamState.Attacking, w);
    }
    //homeTeam.controlPlayer(0);

    numberOfPlayers = 0;
    moved = false;
}

@Override
public void updateMatch(float x, float y, Rain rain, float dt) {
    homeTeam.updateControlledPlayerOnline(x, y);

    rain.update();
    w.step(Constants.GAME_SIMULATION_SPEED, 6, 2);

    if(x != 0 || y != 0) moved = true;      //this is the place the variable is changed, but if it isn't in debug mode, MPClient thinks it's always false
}

public void setPlayerPosition(float x, float y, String name, int team) {
    if(team == 0)
        homeTeam.changePlayerPosition(x, y, name);
    else
        visitorTeam.changePlayerPosition(x, y, name);
}
}
uyhoqukh

uyhoqukh1#

一个月

这是因为它正在阅读match.moved变量的缓存值,而不是最新值。要避免这种情况,请将变量声明为volatile

public volatile boolean moved;

了解更多here

nlejzf6q

nlejzf6q2#

TL;医生

AtomicBooleanvolatile的方便替代。
此类 Package 并保护嵌套基元boolean值,同时确保适当的可见性。
示例化:

public final AtomicBoolean moved = new AtomicBoolean( false ) ;

吸气剂:

boolean x = moved.get()  // Returns current value.

设定器:

moved.set( false ) // Sets a new value.

获取,然后设置:

boolean x = moved.getAndSet( false ) ;  // Retrieves the old value before setting a new value.

AtomicBoolean

Answer by agamagarwal是正确的。您已经陷入了跨线程访问变量时出现的可见性难题。一个解决方案是使用此处显示的volatile
另一个解决方案是与Java捆绑在一起的Atomic…类,在本例中是AtomicBoolean
Atomic…类 Package 一个值,并添加线程安全方法来访问和设置该值。
我经常更喜欢使用Atomic…类而不是volatile,这样做的一个原因是,它可以让用户清楚地看到我们正在跨线程使用受保护的资源。
示例化:

public class MultiPlayMatch extends Match {

public final AtomicBoolean moved = new AtomicBoolean( false ) ;
…

注意关于示例化的两件事:

  • final确保了我们不会用一个AtomicBoolean对象替换另一个对象,这样的替换会让我们回到我们试图逃避的变量可见性难题中。
  • AtomicBoolean对象在示例化外部对象的同时被示例化(在您的例子中是MultiPlayMatch),因此我们已经确保了AtomicBoolean的示例在任何访问之前存在,包括任何跨线程访问,如果我们等到稍后(“lazy” loading),那么我们将陷入我们试图逃避的变量可见性难题。

获取值:

if ( this.match.moved.get() ) { …  // Returns the primitive `true` or `false` value wrapped within this `AtomicBoolean` object.

并设置该值:

this.match.moved.set( false ) ;

您可能希望在获取当前值的同时,在一个即时线程安全的“原子”(组合)操作中设置一个值:

boolean oldValue = this.match.moved.getAndSet( false ) ;

要了解Java中并发的所有信息,请参阅Brian Goetz等人的书Java Concurrency in Practice

相关问题