Java – without a print statement, the loop cannot see the value changed by other threads

In my code, I have a loop waiting to change a state from a different thread Another thread works, but my loop will never see the changed value It waits forever But when I put a system in the loop out. Println statement, it suddenly works! Why?

Here is my code example:

class MyHouse {
    boolean pizzaArrived = false;

    void eatPizza() {
        while (pizzaArrived == false) {
            //System.out.println("waiting");
        }

        System.out.println("That was delicIoUs!");
    }

    void deliverPizza() {
        pizzaArrived = true;
    }
}

When the while loop runs, I call deliverpizza () from another thread to set the pizza arrived variable But the loop is only after I uncomment system out. Println ("wait"); Statement What's going on?

Solution

Allow the JVM to assume that other threads do not change the pizzaarrived variable during the loop In other words, it can promote the pizzaarrived = = false test outside the loop to optimize:

while (pizzaArrived == false) {}

Enter this:

if (pizzaArrived == false) while (true) {}

This is an infinite loop

To ensure that changes made by one thread are visible to other threads, you must always add some synchronization between threads The simplest way is to make the shared variable volatile:

volatile boolean pizzaArrived = false;

Making the variable volatile ensures that different threads can see the impact of each other's changes This prevents the JVM from caching the value of pizza arrived or boosting the test out of the loop Instead, it must read the value of the real variable every time

(more formally, volatile creates a prior relationship between access variables. This means that all other work a thread did is visible to the thread receiving pizza before sending pizza, even if these other changes are not for volatile variables.)

Synchronized methods are mainly used to achieve mutual exclusion (prevent two things from happening at the same time), but they also have the same side effects as volatile Using variables when reading and writing them is another way to make changes visible to other threads:

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;
    }
}

Effect of printing statement

System. Out is a printstream object The printstream method is synchronized as follows:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

Synchronization prevents caching of pizzaarrived during a loop Strictly speaking, two threads must synchronize on the same object to ensure that changes to variables are visible (for example, calling println after setting pizzaArrived and calling it again before reading pizzaArrived will be correct). If only one thread is synchronized on a particular object, the JVM is allowed to ignore it In practice, the JVM is not smart enough to prove that other threads will not call println after setting pizza arrived, so it assumes that they may Therefore, if you call system out. Println, the variable cannot be cached during the loop This is why such a loop works when there are printed statements, although it is not the correct solution

Use system Out is not the only way to cause this effect, but it is what people most often find when they try to debug why their loop doesn't work!

Bigger problem

While (pizza arrived = = false) {} is a busy wait loop That's bad! When it waits, it consumes CPU, which slows down other applications and increases the power consumption, temperature and fan speed of the system Ideally, we want the looping thread to sleep while waiting, so it won't take up CPU

Here are some methods:

Using wait / notify

A low-level solution is to use the wait / notify methods of object:

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();
        }
    }
}

In this version of code, the looping thread calls wait (), which puts the thread into sleep No CPU cycles are used during sleep After the second thread sets the variable, it calls notifyall() to wake up any / all threads waiting for the object It's like a pizza guy ringing the doorbell, so you can sit down and rest and wait instead of standing awkwardly at the door

When calling wait / notify on an object, the synchronization lock of the object must be maintained, which is what the above code does You can use any object you like, as long as both threads use the same object: Here I use this (myhouse instance) Usually, two threads cannot enter the synchronization block of the same object at the same time (this is part of the synchronization purpose), but it works here because the thread temporarily releases the synchronization lock when it is inside the wait () method

Of BlockingQueue

BlockingQueue is used to implement producer - consumer queue The "consumer" takes the goods from the front of the queue, and the "producer" pushes the goods behind An example:

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");
    }
}

Note: the put and take methods of BlockingQueue can throw interruptedexceptions, which are checked exceptions that must be handled In the above code, the exception is thrown again for simplicity You might prefer to catch the exception in the method and retry the put or take call to ensure its success In addition to this, BlockingQueue is very easy to use

No other synchronization is required here, because BlockingQueue ensures that all threads that do before putting items on the queue are visible to the threads that take them out

Executor

Executing a program is like executing a task with an existing blockingqueues Example:

// 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

For details, see the documentation for executor, executorservice, and executors

event processing

Looping is wrong while waiting for the user to click something in the UI Instead, use the event handling function of the UI toolkit In swing, for example:

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");
});

Because the event handler runs on the event dispatch thread, performing long work in the event handler will prevent other interaction with the UI until the work is completed You can start a slow operation on a new thread, or use one of the above techniques (wait / notify, BlockingQueue or executor) to dispatch the schedule to the waiting thread You can also use swingworker designed for this purpose and automatically provide background worker threads:

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

To perform periodic operations, you can use Java util. Timer. It is easier to use, start and stop than writing your own timed loop The presentation prints once per second. Current time:

Timer timer = new Timer();
TimerTask task = new TimerTask() {
    @Override
    public void run() {
        System.out.println(System.currentTimeMillis());
    }
};
timer.scheduleAtFixedRate(task,1000);

Each Java util. Timer has its own background thread to execute its scheduled timertasks Of course, a thread sleeps between tasks, so it does not consume CPU

In swing code, there is a similar javax swing. Timer, but it executes listeners on swing threads, so you can safely interact with swing components without manually switching threads:

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);

Other methods

If you're writing multithreaded code, it's worth exploring the classes in these packages to see what's available:

> java. util. concurrent > java. util. concurrent. atomic > java. util. concurrent. locks

See also concurrency section. In the java tutorial Multithreading is complex, but there is a lot of help available!

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>