Locking and unlocking

When the state is "unlocked", acquire changes the state to "locked"
with the current thread as owner and an acquired count of 1.

When the state is "locked" and the current thread owns the lock acquire
only increments the acquired count.

When the state is "locked" and the current thread does not own the lock
acquireblocks until the owner releases the lock. If the thread that called
acquire is woken upon release of the lock it will take ownership and change
the state to "locked" with an acquired count of 1.

There are two further important properties of acquire:

acquire is single-wakeup. That is, if there are multiple threads blocked on
acquire, and the lock is released, only one thread will be woken up. The
runtime guarantees that the woken thread completes its acquire operation.

When multiple threads are blocked on acquire they are woken up in FIFO
order. This is useful for providing fairness properties of abstractions built
using locks. (Note that this differs from the Python implementation where the
wake-up order is undefined.)

A non-blocking with. tryWith is a convenience function which first tries to
acquire the lock. If that fails, Nothing is returned. If it succeeds, the
computation is performed. When the computation terminates, whether normally or
by raising an exception, the lock is released and Just the result of the
computation is returned.