In JMeter's Synchronizing Timer, what does a Number of Simulated Users to Group by of 0 mean?
answer
- Zero is not the same as ungrouped
- The value is filled in from elsewhere
- Look at the enclosing Thread Group
- Resolved when threads start, not at load
basics
~20 sZero means the whole Thread Group. JMeter leaves the barrier unsized until the first thread starts, then sizes it from that Thread Group's Number of Threads, so the timer releases only once every thread in the group has arrived.
solid answer
~40 s`SyncTimer` wraps a `java.util.concurrent.CyclicBarrier`. At test start it builds the barrier with the configured group size; when the size is `0` it builds an empty wrapper instead, and the first `threadStarted()` callback calls `setup(numThreadsInGroup)` using `JMeterContextService.getContext().getThreadGroup().getNumThreads()`. Every thread that reaches the timer blocks in `delay()` until that many parties have arrived, then all are released together — which is how you make a checkout request fire from every thread at once. The last thread to arrive gets barrier index `0`; the barrier is cyclic, so it re-arms as it trips and the same timer works again on the next iteration. The manual states the same rule: setting the field to `0` is equivalent to setting it to the number of threads in the Thread Group.
go deeper
Recall that this timer blocks threads until a set number are waiting and then releases them together, and that 0 in the group field means the whole Thread Group.
Explain the lazy initialisation: the barrier is created empty, then sized from the Thread Group's Number of Threads on the first threadStarted callback, and shared because the clone copies the reference.
Show you know the barrier is cyclic and re-arms on trip, so it fires each iteration, and that its size must never exceed the threads that can actually reach it.
Decide whether a plan should encode the group size explicitly rather than rely on 0, so a later change to the Thread Group's thread count cannot silently change what the barrier means.
## What the element is The Synchronizing Timer is a barrier, not a delay. Its own documentation says the purpose is "to block threads until X number of threads have been blocked, and then they are all released at once". Unlike every other timer it returns `0` from `delay()` — the waiting happens *inside* `delay()`, on a `CyclicBarrier`, and nothing is handed back to `JMeterThread` to sleep on. It has exactly two fields: - **Number of Simulated Users to Group by** — how many threads must arrive before the barrier opens. - **Timeout in milliseconds** — how long a waiting thread will wait before giving up. `0` means wait indefinitely. ## How zero is resolved The zero case is a lazy initialisation, and the order matters: 1. On test start, `createBarrier()` runs. With a group size above zero it constructs `new CyclicBarrier(groupSize)` immediately. With group size `0` it constructs a wrapper holding no barrier yet. 2. As each thread starts, `threadStarted()` fires. If the group size is `0`, it reads `JMeterContextService.getContext().getThreadGroup().getNumThreads()` and calls a synchronized `setup(parties)` that creates the barrier once and only once. 3. From then on every thread that reaches the timer calls `await` on that one shared barrier. So the number that ends up in the barrier is the Thread Group's **Number of Threads** field, not the number of threads currently running, and not the number of threads across the whole plan. The barrier object itself is shared because `SyncTimer.clone()` deliberately copies the reference rather than the object — the class comment calls this out as the cross-thread communication the element needs. ## Why the barrier can be reused `CyclicBarrier.await` returns an arrival index: the last thread to arrive gets `0`. Reuse is not something `SyncTimer` arranges — *cyclic* means the barrier re-arms itself the instant it trips, inside the last thread's own `await`, and that is what lets a Synchronizing Timer in a looping Thread Group fire a synchronised checkout on every iteration, not just the first. What `SyncTimer` adds is a `finally` block calling `barrier.reset()` whenever its local `arrival` is `0` — and `arrival` is *still* `0` when `await` threw, so this really earns its keep on the timeout and interrupt paths, where the barrier has been marked broken and would otherwise fail every later `await` for the rest of the run. The price is that a reset also breaks the barrier for any thread that has already re-entered `await` for the next round: it catches `BrokenBarrierException`, returns `0`, and skips the synchronisation rather than being helped by it. ## Choosing the value | Setting | Effect | When it fits | |---|---|---| | `0` | barrier sized from the Thread Group's Number of Threads | you want the whole group to move together | | a value equal to Number of Threads | identical behaviour, written explicitly | you want the plan to be self-documenting | | a value below Number of Threads | threads are released in packs of that size | you want repeated smaller simultaneous groups | | a value above Number of Threads | the barrier can never fill | always a mistake | The last row is the trap. Nothing validates the field against the Thread Group, so a group size of `100` under a Thread Group of `50` threads simply blocks forever when the timeout is `0`. ## Details that separate a good answer - **The barrier lives in one JVM.** It coordinates only the threads running in the same JMeter process; coordinating across separate engines is a different problem and a different part of the tool. - **Ramp-up couples to it.** With group size `0` the first arriving thread waits until the last thread has started *and* reached the timer, so a long ramp-up turns into a long block at the barrier. - **A negative timeout is rejected at run time.** `delay()` throws `IllegalArgumentException` with the message naming the timer when `timeoutInMs` is below zero. - **The scheduler caps the wait.** Even with a timeout of `0`, the wait is passed through `TimerService.adjustDelay`, which shortens it so it cannot run past a scheduled end time. - **`timer.factor` does not touch it.** That property only scales timers implementing `ModifiableTimer`, which the Synchronizing Timer is not.
- What concurrency primitive backs JMeter's Synchronizing Timer, and why does that matter?A java.util.concurrent.CyclicBarrier, shared between threads because SyncTimer.clone copies the barrier reference. Cyclic is the important word: the barrier re-arms itself the moment it trips, so the same timer releases a fresh group on every iteration rather than working once.
- How much does a Synchronizing Timer contribute to the sleep JMeterThread performs?Nothing. Its delay() blocks the calling thread on the barrier and then returns 0, so the thread has already done its waiting inside the timer. Any other timer in the same scope still adds its own delay to the sum.
saying these in an interview costs you the question
- Says 0 disables the timer or means no grouping at all.
- Thinks 0 means all threads in the whole test plan.
- Believes the barrier resizes as running thread counts change.
- Assumes the timer works only once per test run.
- Claims the Synchronizing Timer adds its wait to the summed sleep.