I think this example is better:
class Foo{
int x;
}
Thread1:
Foo foo = new Foo();
foo.x = 10; (1)
concurrentMap.put("1", foo); (2)
Thread2:
Foo foo = concurrentMap.get("1"); (3)
if(foo!=null) print(foo.x); (4)
There is a happens-before edge between (1) and (2) due to program-order rule. And also between (3) and (4) there is a happens-before edge due to program-order rule.
And if thread2 sees the non null value, then there is a happens-before edge between (2) and (3) due to either the volatile variable rule or monitor lock rule (often this is called memory consistency effects on e.g. queues)
Since the happens-before relation is transitive, there is a happens-before edge between (1) and (4).