The simplest correct pattern uses an atomic set-if-not-exists with a timeout. In Redis this is SET resource_key random_token NX PX 30000, which means create the key only if it does not exist and auto-delete it after 30 seconds. The random token is the owner's proof of ownership. To release, the owner runs a small Lua script that deletes the key only if the stored token still matches its own. That match-then-delete must be atomic, otherwise you can delete a lock that already expired and was grabbed by someone else.
ZooKeeper and etcd take a different approach built on consensus. A client creates an ephemeral node, and ZooKeeper deletes that node automatically when the client's session ends, which happens once the session timeout passes without hearing from the client. Clients line up in order and watch the node ahead of them, so the lock is handed off cleanly without polling. Because these systems run a quorum (typically Raft in etcd, Zab in ZooKeeper), they survive node failures and give a consistent view of who holds the lock.
The subtle failure is the paused holder. A process can acquire the lock, then get frozen by a long garbage collection pause or a network partition. Its lease expires, someone else takes the lock, and then the original process wakes up still believing it holds the lock and writes to the resource. The defense is a fencing token: a number that only ever increases, handed out with each lock grant. The protected resource rejects any write carrying a token lower than the highest one it has seen, so the stale holder's write is refused.