Java Concurrency JDK 1.6: Busy waiting is better than signaling? Effective Java # 51

Joshua Bloch "Effective Java", paragraph 51 is independent of the thread scheduler, and also does not require unnecessary threads in runnable state. Quoted text:

The main method of keeping the number of running threads down is to do a little work for each thread, and then wait for some state using Object.wait or for some time that needs to be done with Thread.sleep. Threads should not be busy - wait, repeatedly checking the data structure, expecting something. Besides the fact that the program is vulnerable to the vagaries of the scheduler, waiting for the wait can significantly increase the load on the processor, reducing the amount of useful work that other processes on the same machine can perform.

And then a microobject of lively expectation is shown against the proper use of signals. In the book, busy waiting makes 17 round trips / s, while the wait / notify version is 23,000 rounds per second.

However, when I tried the same JDK 1.6 benchmark, I see the exact opposite: the wait waits for 760K roundtrips / second, while the wait / notify version is 53.3K roundtrips / s, that is, wait / notify should have been ~ 1400 times faster but turned out to be ~ 13 times slower?

I understand that busy expectations are not very good, and the signaling is even better - CPU usage is ~ 50% in the expected version of the wait, while it remains ~ 30% in waiting / notification - but is there anything that explains the numbers?

If this helps, I run JDK1.6 (32 bit) on Win 7 x64 (i5 core).

UPDATE Source below. To start a busy desktop, change the base class PingPongQueue to BusyWorkQueue import java.util.LinkedList; import java.util.List;

abstract class SignalWorkQueue { private final List queue = new LinkedList(); private boolean stopped = false; protected SignalWorkQueue() { new WorkerThread().start(); } public final void enqueue(Object workItem) { synchronized (queue) { queue.add(workItem); queue.notify(); } } public final void stop() { synchronized (queue) { stopped = true; queue.notify(); } } protected abstract void processItem(Object workItem) throws InterruptedException; private class WorkerThread extends Thread { public void run() { while (true) { // Main loop Object workItem = null; synchronized (queue) { try { while (queue.isEmpty() && !stopped) queue.wait(); } catch (InterruptedException e) { return; } if (stopped) return; workItem = queue.remove(0); } try { processItem(workItem); // No lock held } catch (InterruptedException e) { return; } } } } } // HORRIBLE PROGRAM - uses busy-wait instead of Object.wait! abstract class BusyWorkQueue { private final List queue = new LinkedList(); private boolean stopped = false; protected BusyWorkQueue() { new WorkerThread().start(); } public final void enqueue(Object workItem) { synchronized (queue) { queue.add(workItem); } } public final void stop() { synchronized (queue) { stopped = true; } } protected abstract void processItem(Object workItem) throws InterruptedException; private class WorkerThread extends Thread { public void run() { final Object QUEUE_IS_EMPTY = new Object(); while (true) { // Main loop Object workItem = QUEUE_IS_EMPTY; synchronized (queue) { if (stopped) return; if (!queue.isEmpty()) workItem = queue.remove(0); } if (workItem != QUEUE_IS_EMPTY) { try { processItem(workItem); } catch (InterruptedException e) { return; } } } } } } class PingPongQueue extends SignalWorkQueue { volatile int count = 0; protected void processItem(final Object sender) { count++; SignalWorkQueue recipient = (SignalWorkQueue) sender; recipient.enqueue(this); } } public class WaitQueuePerf { public static void main(String[] args) { PingPongQueue q1 = new PingPongQueue(); PingPongQueue q2 = new PingPongQueue(); q1.enqueue(q2); // Kick-start the system // Give the system 10 seconds to warm up try { Thread.sleep(10000); } catch (InterruptedException e) { } // Measure the number of round trips in 10 seconds int count = q1.count; try { Thread.sleep(10000); } catch (InterruptedException e) { } System.out.println(q1.count - count); q1.stop(); q2.stop(); } } 
+6
java multithreading synchronization concurrency
source share
4 answers

In your test, the queue constantly receives new elements, so waiting-waiting is very low waiting.

If the queue receives one new item every 1 ms, you can see that busy-waiting will spend more time burning CPUs. This will slow down another part of the application.

So it depends. If you are busy, wait at the user login, this is definitely wrong; while waiting in carefree data structures such as AtomicInteger is certainly good.

+6
source share

Yes, waiting for an answer will respond faster and execute more cycles, but I think that the fact is that it imposes a disproportionately heavier load on the entire system.

Try to run 1000 busy wait threads vs 1000 wait / notify threads and check overall throughput.

I think the difference that you are observing is probably the sun, which the compiler optimizes for what people do, and not what people should do. The sun does this all the time. The initial benchmark in the book may have been caused by some planner error that Sun fixed - with this ratio, this certainly sounds wrong.

+3
source share

It depends on the number of threads and the degree of conflict: Busy is waiting badly if it happens often and / or consumes many processor cycles.

But atomic integers (AtomicInteger, AtomicIntegerArray ...) are better than synchronizing Integer or int [], even waiting for a thread is also executed.

Use java.util.concurrent package and in your case ConcurrentLinkedQueueas as often as possible

+1
source share

Busy waiting is not always a bad thing. The “correct” (on a low-level) way of doing things — using Java synchronization primitives — carries the overhead, often significant, of the accounting necessary to implement general-purpose mechanisms that perform fairly well in most scenarios. On the other hand, busy waiting is very easy, and in some situations it can be quite large compared to synchronizing one size. While synchronization based solely on lively waiting definitely does not matter in any general settings, it is very useful. This is true not only for Java - spinlocks (a fancy name for locks with expected wait) are widely used, for example, in database servers.

In fact, if you go through the sources of java.util.concurrent packages, you will find many places that contain “tricky”, seemingly fragile code. I find SynchronousQueue good example (you can look at the source in the JDK distribution or here , both OpenJDK and Oracle seem to use the same implementation). Busy waiting is used as optimization - after a certain number of “spins”, the thread goes into a proper “sleep”. In addition, he has other subtleties - unstable use, depending on the number of processors, etc. It really ... illuminates, thereby showing what is needed to implement an effective low-level concurrency. Moreover, the code itself is really clean, well-documented, and high-quality overall.

0
source share

All Articles