java - Loop doesn't see changed value without a print statement -
in code have loop waits state changed different thread. other thread works, loop never sees changed value. it waits forever. however, when put system.out.println
statement in loop, works! why?
the following example of code:
class myhouse { boolean pizzaarrived = false; void eatpizza() { while (pizzaarrived == false) { //system.out.println("waiting"); } system.out.println("that delicious!"); } void deliverpizza() { pizzaarrived = true; } }
while while loop running, call deliverpizza()
different thread set pizzaarrived
variable. loop works when uncomment system.out.println("waiting");
statement. what's going on?
the jvm allowed assume other threads not change pizzaarrived
variable during loop. in other words, can hoist pizzaarrived == false
test outside loop, optimizing this:
while (pizzaarrived == false) {}
into this:
if (pizzaarrived == false) while (true) {}
which infinite loop.
to ensure changes made 1 thread visible other threads must add synchronization between threads. simplest way make shared variable volatile
:
volatile boolean pizzaarrived = false;
making variable volatile
guarantees different threads see effects of each other's changes it. prevents jvm caching value of pizzaarrived
or hoisting test outside loop. instead, must read value of real variable every time.
(more formally, volatile
creates happens-before relationship between accesses variable. means all other work thread did before delivering pizza visible thread receiving pizza, if other changes not volatile
variables.)
synchronized methods used principally implement mutual exclusion (preventing 2 things happening @ same time), have same side-effects volatile
has. using them when reading , writing variable way make changes visible other threads:
class myhouse { boolean pizzaarrived = false; void eatpizza() { while (getpizzaarrived() == false) {} system.out.println("that delicious!"); } synchronized boolean getpizzaarrived() { return pizzaarrived; } synchronized void deliverpizza() { pizzaarrived = true; } }
the effect of print statement
system.out
printstream
object. methods of printstream
synchronized this:
public void println(string x) { synchronized (this) { print(x); newline(); } }
the synchronization prevents pizzaarrived
being cached during loop. strictly speaking, both threads must synchronize on same object guarantee changes variable visible. (for example, calling println
after setting pizzaarrived
, calling again before reading pizzaarrived
correct.) if 1 thread synchronizes on particular object, jvm allowed ignore it. in practice, jvm not smart enough prove other threads won't call println
after setting pizzaarrived
, assumes might. therefore, cannot cache variable during loop if call system.out.println
. that's why loops work when have print statement, although not correct fix.
using system.out
not way cause effect, 1 people discover often, when trying debug why loop doesn't work!
the bigger problem
while (pizzaarrived == false) {}
busy-wait loop. that's bad! while waits, hogs cpu, slows down other applications, , increases power usage, temperature, , fan speed of system. ideally, loop thread sleep while waits, not hog cpu.
here ways that:
using wait/notify
a low-level solution use 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 delicious!"); } void deliverpizza() { synchronized (this) { pizzaarrived = true; this.notifyall(); } } }
in version of code, loop thread calls wait()
, puts thread sleep. not use cpu cycles while sleeping. after second thread sets variable, calls notifyall()
wake any/all threads waiting on object. having pizza guy ring doorbell, can sit down , rest while waiting, instead of standing awkwardly @ door.
when calling wait/notify on object must hold synchronization lock of object, above code does. can use object long both threads use same object: here used this
(the instance of myhouse
). usually, 2 threads not able enter synchronized blocks of same object simultaneously (which part of purpose of synchronization) works here because thread temporarily releases synchronization lock when inside wait()
method.
blockingqueue
a blockingqueue
used implement producer-consumer queues. "consumers" take items front of queue, , "producers" push items on @ back. example:
class myhouse { final blockingqueue<object> queue = new linkedblockingqueue<>(); void eatfood() throws interruptedexception { // take next item queue (sleeps while waiting) object food = queue.take(); // , system.out.println("eating: " + food); } void deliverpizza() throws interruptedexception { // in producer threads, push items on queue. // if there space in queue can return immediately; // consumer thread(s) later queue.put("a delicious pizza"); } }
note: put
, take
methods of blockingqueue
can throw interruptedexception
s, checked exceptions must handled. in above code, simplicity, exceptions rethrown. might prefer catch exceptions in methods , retry put or take call sure succeeds. apart 1 point of ugliness, blockingqueue
easy use.
no other synchronization needed here because blockingqueue
makes sure threads did before putting items in queue visible threads taking items out.
executors
executor
s ready-made blockingqueue
s execute tasks. example:
// "singlethreadexecutor" has 1 work thread , unlimited queue executorservice executor = executors.newsinglethreadexecutor(); runnable eatpizza = () -> { system.out.println("eating delicious pizza"); }; runnable cleanup = () -> { system.out.println("cleaning house"); }; // submit tasks executed on work thread executor.execute(eatpizza); executor.execute(cleanup); // continue without needing wait tasks finish
for details see doc executor
, executorservice
, , executors
.
event handling
looping while waiting user click in ui wrong. instead, use event handling features of ui toolkit. in swing, example:
jlabel label = new jlabel(); jbutton button = new jbutton("click me"); button.addactionlistener((actionevent e) -> { // event listener run when button clicked. // don't need loop while waiting. label.settext("button clicked"); });
because event handler runs on event dispatch thread, doing long work in event handler blocks other interaction ui until work finished. slow operations can started on new thread, or dispatched waiting thread using 1 of above techniques (wait/notify, blockingqueue
, or executor
). can use swingworker
, designed this, , automatically supplies background worker thread:
jlabel label = new jlabel(); jbutton button = new jbutton("calculate answer"); // add click listener button button.addactionlistener((actionevent e) -> { // defines myworker swingworker result type string: class myworker extends swingworker<string,void> { @override public string doinbackground() throws exception { // method called on background thread. // can long work here without blocking ui. // example: thread.sleep(5000); return "answer 42"; } @override protected void done() { // method called on swing thread once work done string result; try { result = get(); } catch (exception e) { throw new runtimeexception(e); } label.settext(result); // display "answer 42" } } // start worker new myworker().execute(); });
timers
to perform periodic actions, can use java.util.timer
. easier use writing own timing loop, , easier start , stop. demo prints current time once per second:
timer timer = new timer(); timertask task = new timertask() { @override public void run() { system.out.println(system.currenttimemillis()); } }; timer.scheduleatfixedrate(task, 0, 1000);
each java.util.timer
has own background thread used execute scheduled timertask
s. naturally, thread sleeps between tasks, not hog cpu.
in swing code, there javax.swing.timer
, similar, executes listener on swing thread, can safely interact swing components without needing manually switch 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 ways
if writing multithreaded code, worth exploring classes in these packages see available:
and see concurrency section of java tutorials. multithreading complicated, there lots of available!
Comments
Post a Comment