简体   繁体   中英

How do I use wait() and notify correctly with threads in java?

I'm making a 2D game in android studio and I've been sitting on a problem for a few days now. I want to stop my thread Gravity, so that my player can jump. When the player is done jumping, the gravity thread can continue.

I searched on the internet for how to use the wait() and notify() with the syncronised block. But when I try to apply it, my Gravity won't stop. If someone can show me how to use this in the correct way, I would be overjoyed...

This is my code in my scene class to start the jumping.

public void recieveTouch(MotionEvent event) {

    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            if(player.getPoint().y > 0) {
                playerJump();
            }
    }
}

public void playerJump(){
    playerJump = new Jump(player, this);
    thread = new Thread(playerJump);
    thread.setDaemon(true);
    thread.start();
}

This is my Gravity thread.

public class Gravity implements Runnable {

private Player player;
private GameScene gameScene;

public Gravity (Player player, GameScene gameScene){
    this.player = player;
    this.gameScene = gameScene;
}

@Override
public void run(){
    while(true){

        player.playerMoveDown(3);
        gameScene.update();

        try {
            Thread.sleep(25);
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

And this is my Jump thread.

public class Jump implements Runnable {

private Player player;
private GameScene gameScene;

public Jump (Player player, GameScene gameScene){
    this.player = player;
    this.gameScene = gameScene;
}

@Override
public void run(){
    int eindHoogte = player.getPoint().y - 60;

        while (player.getPoint().y > eindHoogte) {
            player.playerMoveUp(3);
            gameScene.update();

            try {
                Thread.sleep(25);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }
}

Assuming that you are passing the same player object to Jump and Gravity class, you can synchronize on the player and maintain a flag for jump complete operation.

This will ensure that while the player is performing the jump operation gravity will wait for the jump operation and will only continue again if the player jumps

flag is used just to prevent the case where notification sent by calling the notify() method by one thread is missed when the other thread has not called wait()

see the example code below

    public class Jump implements Runnable {

    private Player player;
    private GameScene gameScene;

    public Jump (Player player, GameScene gameScene){
        this.player = player;
        this.gameScene = gameScene;
    }

    @Override
    public void run(){
        int eindHoogte = player.getPoint().y - 60;

        while (player.getPoint().y > eindHoogte) {
                player.playerMoveUp(3);
                gameScene.update();

                try {
                    Thread.sleep(25);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }

        synchronized(player){

            // acts as a signal to gravity thread to start processing the gravity operation
            player.setJumpComplete(true);
        }

    }

}


    public class Gravity implements Runnable {

        private Player player;
        private GameScene gameScene;

        public Gravity (Player player, GameScene gameScene){
            this.player = player;
            this.gameScene = gameScene;
        }

        @Override
        public void run(){



            while(true){

                synchronized (player) {

                    // if player has completed the jump then process the gravity operation else just wait for jump to complete
                    if(player.isJumpComplete()){
                        player.playerMoveDown(3);
                        gameScene.update();

                        // reset the jump flag and wait for the next jump operation to complete
                        player.setJumpComplete(false);
                    }

                }

                try {
                    Thread.sleep(25);
                }
                catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }

Let me give you a genral answer which would be helpful for not only this case but for future this can assist gou as a mini guide. Very basic elements of multithreading when working in Java language are

  1. Synchronized block

  2. an Object, lock (for convenience and easy understanding lets name it like this) on which you apply synchronized block to acquire monitor.

  3. wait() method of object, lock

  4. notify() or notifyAll() method of object, lock

  5. Thread objects and may be you need class which implements Runnable intetface.

Points to take care are as below.

  1. You should only invoke wait(), notify() or notifyAll() on the object upon which current thread has acquired the monitor.

    1. 1 sleep() is a method of Thread.java and wait() is a method of Object.java .

    1.2 sleep() does not relinquishes the lock if it has been acquired whereas wait() relinquishes it. It is not mandatory to invoke sleep after taking a lock where as for wait it is mandatory.

    1.3 Also prefer notifyAll() over notify(), as notify may only signal any single thread to wake up and try to acquire the lock, where as notifyAll() wakes up all the eligible waitng threads and allows a competition for acquiring the lock, so notify has better chances of equal opportunity for all threads .

  2. Usually a Thread executes a process once and calls notifyAll() and waits() until it is notifies again. Also in case it is interrupted, the usual approach is to terminate the thread. Upon execution of wait method Thread relinquishes the monitor, also as it has invoked notifyAll method on lock object before invoking wait method so all other thread eligible for aquiring monitor of lock are awakened. Thread scheduler assigns the monitor to one of the thread, it aslo executes a command invokes notifyAll on lock and then invokes wait on lock. Now this second thread also goes in wait state for re acuiring the monitor and some other thread meanwhile acquires the monitor and this entire process continues till we do not interrupt all the threads.

The example of such a model you can refer from my answer that I wrote for another question

https://stackoverflow.com/a/42049397/504133

Just make sure while working with android you do not modify the main GUI from any other thread other than main GUI thread, and if you wish to do so, use Handlers

On the subject of notify/wait, the answers above are suitable so I won't add anything here, however I think you can solve your issue in another way, by modeling it more like real life. Whereas gravity is an external force acting on the player and it does make sense to have a gravity thread modifying the player position, it's not really the same for a jump. The jump is something the player is doing itself, so it's a property of the player object.

I would rework the design to : - give the player a motion property which is a list of effect objects, one which is a Gravity effect, and one which is a jump effect, one which is a walking effect, and each of which provide some movement offset, while active. - turn the Gravity thread into a Motion thread which retrieves the movement offset from each of the players effects, adds it up and then moves the player the total - have the jump event simply turn the jump effect on or off - leave the gravity effect on unless the player gets an anti gravity suit or something

Pseudo java code since I'm typing this on my phone)

interface Effect {
   method Point getMotion(duration);
   method Boolean isCompleted();
}

class Jump implements Effect {
  ///...
}

class sceneObject {  

  synchronized  List<Effect> movementEffects;

  Point position;

  void move(Point);

  void addEffect(Effect);

  void removeEfects(Class);

   void Point getTotalMotion(duration) {
       Point result;
       for each effect in movementEffects {
              result += effect. getMotion(this, duration);
              if effect.isCompleted() {
                     removeEffect(effect)
              }
       }
       return result;
    }
    void applyMotion(duration) {
         move(getTotalMotion(duration))
    }
}

class player extends SceneObject {

   Effect gravity = new Gravity(3);

   Player () {
           addEffect(gravity);
   }

    void jump() {
           addEffect(new Jump)
    }

    void walkLeft() {
           stopWalking();
           addEffect(new Walk(-3))
    }

    void stopWalking() {
           removeEffects(Walk.class)
   }
}

class EffectsThread {
      void run() {
          While (...)  {
                timeSinceLastIteration = ... ;

                for each item in scene {
                      item.applyMotion(timeSinceLastIteration);
                }
                //sleep
           }
      }
}

and then you'll also only need one effects thread to be running, and you never need to start and stop it

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM