2

Edit: a user marked that my question is a possible duplicate of this question: "What is the volatile keyword useful for", whose title is "What is the volatile keyword useful for?". I read the question but I don't see how it relates to my question.


Here is a program written in two .java files. My question involves the if..else.. in the main method.

Note in the code below, the single line within the else {..} is commented out. I'll call this "version 1" of the program, and I'll call the program with that line commented back in "version 2".

// -------------
// The code below is in IfElseBugProgram.java
public class IfElseBugProgram {

    public static void main(String[] args) {

        MyJFrame terminal = new MyJFrame();

        while (true) {
            String keyReleased = terminal.getKeyReleased();

            if (! keyReleased.equals("") )
            {
                System.out.print("@" + keyReleased);
            }
            else
            {
                // System.out.print("!" + keyReleased);
            }

        } 
    }
}



// ----- 
//The code below is in file MyJFrame.java

import javax.swing.JFrame;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import java.util.ArrayList;
import java.util.List;


public class MyJFrame extends JFrame implements KeyListener
{
    private List<KeyEvent> keyEventQueue;

    public MyJFrame()
    {
        keyEventQueue = new ArrayList<KeyEvent>();

        this.addKeyListener(this);
        pack();

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

    public void keyPressed(KeyEvent e)
    {
    }

    public void keyTyped(KeyEvent e)
    {
    }

    public void keyReleased(KeyEvent keyEvent)
    {
        keyEventQueue.add(keyEvent);
        System.out.println("some key was released!" + keyEventQueue.size());
    }


    public String getKeyReleased()
    {
        int i = keyEventQueue.size();

        if (i == 0)
        {
            return ("");
        }
        else
        {
            return ("" + i);
        }
    }

}

I expect that the code in the if {...} to be run, once I press a key on my keyboard. That is, I expect the System.out.print("@" + keyReleased); code to be run as soon as I press a key.

With version 1, I never seem to get System.out.print("@" + keyReleased); to be run; there are never "@1" or "@2" or "@3"s etc printed in the console.

With version 2 (that is, with the code in the else {..} block commented back in),

  • what USUALLY happens is that the print statement that prints out "!" gets run repeatedly, until I press a key. At that point, things like "@1" or "@2" etc get repeatedly printed.
  • what SOMETIMES happens is that I get no "!" nor "@1" or "@2" printed out! (With the same source code!)

Question: Why does the System.out.print("@" + keyReleased); line in the if {..} block not run in version 1, but (usually) does in version 2?


Additional Observations:

  1. The print statement System.out.println("some key was released!" + keyEventQueue.size()); in MyJFrame#keyReleased() always prints out, no matter how I change the code.

  2. I thought that perhaps something might be wrong with my console in Netbeans. But I tried running other code in the if {..} block, such as the line of code java.awt.Toolkit.getDefaultToolkit().beep();, which makes a sound. I also tried making some graphics display in the JFrame itself. The result is the same: with the line of code in the else {...} commented out, the code in the if {..} block doesn't seem to run; but if I comment the line of code in the else {..} back in, the code in the if {...} block (including making a sound or displaying something in the JFrame) does in fact run.

  3. I'm pretty sure that the condition of the if, if (! keyReleased.equals("") ) is correct. I tried adding the one line of code System.out.println("?" + (! keyReleased.equals("")) ); just above the if (! keyReleased.equals("") ), and consistently on my console I would get "?false" printed repeatedly, until I pressed a key, at which point I got "?true" printed repeatedly.

    But also: weirdly, putting in this one line of code (System.out.println("?" + (! keyReleased.equals("")) );) above the if in version 1 causes the lines of code in the if {..} block to now run?!


(optional) Background:

This section explains the code I am ultimately trying to write. If you wish to suggest an approach that might get me to this goal, that is different than the approach I'm using above, please feel free to suggest it.

I am trying to build a simple class called MyJFrame which can help motivate a friend learn how to program. The idea is to allow him to learn about variables in a visually motivated way, by programming extremely simple games, just like I learned when learning BASIC as a child. I want him to be able to write a program, completely contained in one main() method; I want him to have a simple way to draw a string on the screen, and a simple way to get user input. An example program might look like this:

int playerLocationX = 3;
int playerLocationY = 4;

MyJFrame terminal = new MyJFrame();

while(true)
{
  // erase player that was drawn during the previous iteration of this loop
  terminal.write(" ", playerLocationX, playerLocationY);

  // get a key the user last pressed (if the user pressed a key) and update 
  // player location

  String keyReleased = terminal.getKeyReleased();

  if (keyReleased.equals("LEFT"))
  {
    playerLocationX = playerLocationX - 1;
  }
  else if (keyReleased.equals("RIGHT"))
  {
    playerLocationY = playerLocationY + 1;
  }

  // draw player again, using most recent player location
  terminal.write("@", playerLocationX, playerLocationY);

}

I don't want to have him need to write his own keyReleased() method (ie, implementing a KeyListener interface), because this requires knowledge of writing your own methods; it also requires knowledge of objects, because a keyReleased() method has no way of modifying the local variables playerLocationX and playerLocationY stored in main().

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
silph
  • 316
  • 2
  • 9
  • Possible duplicate of [What is the volatile keyword useful for](https://stackoverflow.com/questions/106591/what-is-the-volatile-keyword-useful-for) – that other guy Jul 05 '19 at 17:24

1 Answers1

2

The Java VM is allowed to optimize consecutive, unsynchronized loads by assuming that a variable will not be modified by concurrent threads.

If you want a spin loop over a field that is going to be changed by another thread, you can have the VM make sure every read sees the latest changes by marking it as volatile:

private volatile List<KeyEvent> keyEventQueue;

As the JLS puts it:

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

I don't know whether your V2 is guaranteed to work according to the JLS, but the System.out PrintStream will synchronize on every write, restricting the optimizations that the VM is allowed to do.

that other guy
  • 116,971
  • 11
  • 170
  • 194
  • (I don't know enough about concurrency (eg, what is an unsynchronized load?) to fully understand your answer, but it does alert me to at least the /idea/ of volatile. it may come in handy.

    do you understand why uncommenting a line in the else {..} block, or even putting a single print statement (`System.out.println("?" + (! keyReleased.equals(""))`) about the if (), could cause print statement in the if {..} block to execute, when that code in the if {..} block did not execute otherwise? Could making either of these two changes somehow change the optimization that the Java VM does?
    – silph Jul 05 '19 at 20:54
  • upon re-reading, i see from your answer that you give a hint about why a System.outprintln("") statement before the `if ()` might restrict optimizations; and it sounds like you still don't know why merely uncommenting the line in the else {..} block also seem to restrict optimizations. – silph Jul 05 '19 at 21:00
  • 1
    It doesn't matter whether it's in the `else` block or before the `if`, as long as it happens between two calls to `getKeyReleased`. If I had to speculate, I would guess that part of the VM's implementation of `synchronize` blocks (as performed by [println](https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/io/PrintStream.java#L804)) is to flush the current thread's pending writes and invalidating its cached reads. – that other guy Jul 05 '19 at 21:07