My dive into the specifics (of e.g. the volatile load) probably confused the issue. Nothing on either side actually establishes a happens-before relationship between e.g. seg.count write in put() in Thread1 and a seg.count read in size() in Thread2... It's up to an [optional] something else to establish that, if at all. There are basically three possibilities:
1. Writes in the put() in Thread1 happen-before Reads in size() in Thread2 (via some other happens-before-establishing-thing on each side, one occurring after the return from put() on Thread1, and the other occurring before the call to size() in Thread2).
2. The Reads in size() in Thread2 happen-before the Writes in put() in Thread1 (via some other happens-before-establishing-thing on each side, one occurring after the return from size() in Thread2, and the other occurring before the call to put() in Thread1).
3. There is no happens-before-establishing-thing (in either direction) between the put() call or return in Thread1 and the size() call or return in Thread2.
If it's 1, the happens-before-establishing-things placed after the [Thread1] return from put() and before the [Thread2] call to size() establishes that e.g. the write to seg.count in put() happens-before the read of seg.count in size(). [and will therefore return the size after the put]
If it's 2, the happens-before-establishing-things placed after the [Thread2] return from size() and before the [Thread1] call to put() establishes that e.g. the read of seg.count in size() happens-before the write to seg.count in put() [and will therefore return the size before the put]
If it's 3, it's irrelevant, as no happens-before-establishing stuff means that size() is experiencing concurrent modification, and in the presence of concurrent modification, size() is allowed to return a stale notion of size and does not need an established happens-before relationship to the put()'s writes.