@Override protected OwnerQueueElement<Thread> acquire( LockedEntity key ) { OwnerQueueElement<Thread> suggestion = new OwnerQueueElement<>( currentThread() ); for ( ; ; ) { OwnerQueueElement<Thread> owner = locks.putIfAbsent( key, suggestion ); if ( owner == null ) { // Our suggestion was accepted, we got the lock return suggestion; } Thread other = owner.owner; if ( other == currentThread() ) { // the lock has been handed to us (or we are re-entering), claim it! owner.count++; return owner; } // Make sure that we only add to the queue once, and if that addition fails (because the queue is dead // - i.e. has been removed from the map), retry form the top of the loop immediately. if ( suggestion.head == suggestion ) // true if enqueue() has not been invoked (i.e. first time around) { // otherwise it has already been enqueued, and we are in a spurious (or timed) wake up if ( !owner.enqueue( suggestion ) ) { continue; // the lock has already been released, the queue is dead, retry! } } parkNanos( key, maxParkNanos ); } }
@Override @SuppressWarnings( "SynchronizationOnLocalVariableOrMethodParameter" ) protected void release( LockedEntity key, OwnerQueueElement<Thread> ownerQueueElement ) { if ( 0 == --ownerQueueElement.count ) { Thread nextThread; synchronized ( ownerQueueElement ) { nextThread = ownerQueueElement.dequeue(); if ( nextThread == currentThread() ) { // no more threads in the queue, remove this list locks.remove( key, ownerQueueElement ); // done under synchronization to honour definition of 'dead' nextThread = null; // to make unpark() a no-op. } } unpark( nextThread ); } }
@Override protected OwnerQueueElement<Thread> acquire( LockedEntity key ) { OwnerQueueElement<Thread> suggestion = new OwnerQueueElement<>( currentThread() ); for ( ; ; ) { OwnerQueueElement<Thread> owner = locks.putIfAbsent( key, suggestion ); if ( owner == null ) { // Our suggestion was accepted, we got the lock return suggestion; } Thread other = owner.owner; if ( other == currentThread() ) { // the lock has been handed to us (or we are re-entering), claim it! owner.count++; return owner; } // Make sure that we only add to the queue once, and if that addition fails (because the queue is dead // - i.e. has been removed from the map), retry form the top of the loop immediately. if ( suggestion.head == suggestion ) // true if enqueue() has not been invoked (i.e. first time around) { // otherwise it has already been enqueued, and we are in a spurious (or timed) wake up if ( !owner.enqueue( suggestion ) ) { continue; // the lock has already been released, the queue is dead, retry! } } parkNanos( key, maxParkNanos ); } }
@Override @SuppressWarnings( "SynchronizationOnLocalVariableOrMethodParameter" ) protected void release( LockedEntity key, OwnerQueueElement<Thread> ownerQueueElement ) { if ( 0 == --ownerQueueElement.count ) { Thread nextThread; synchronized ( ownerQueueElement ) { nextThread = ownerQueueElement.dequeue(); if ( nextThread == currentThread() ) { // no more threads in the queue, remove this list locks.remove( key, ownerQueueElement ); // done under synchronization to honour definition of 'dead' nextThread = null; // to make unpark() a no-op. } } unpark( nextThread ); } }
@Test public void shouldFormLinkedListOfWaitingLockOwners() { // given ReentrantLockService.OwnerQueueElement<Integer> queue = new ReentrantLockService.OwnerQueueElement<>( 0 ); ReentrantLockService.OwnerQueueElement<Integer> element1 = new ReentrantLockService.OwnerQueueElement<>( 1 ); ReentrantLockService.OwnerQueueElement<Integer> element2 = new ReentrantLockService.OwnerQueueElement<>( 2 ); ReentrantLockService.OwnerQueueElement<Integer> element3 = new ReentrantLockService.OwnerQueueElement<>( 3 ); ReentrantLockService.OwnerQueueElement<Integer> element4 = new ReentrantLockService.OwnerQueueElement<>( 4 ); // when queue.enqueue( element1 ); // then assertEquals( 1, queue.dequeue().intValue() ); // when queue.enqueue( element2 ); queue.enqueue( element3 ); queue.enqueue( element4 ); // then assertEquals( 2, queue.dequeue().intValue() ); assertEquals( 3, queue.dequeue().intValue() ); assertEquals( 4, queue.dequeue().intValue() ); assertEquals( "should get the current element when dequeuing the current head", 4, queue.dequeue().intValue() ); assertNull( "should get null when dequeuing from a dead list", queue.dequeue() ); assertNull( "should get null continuously when dequeuing from a dead list", queue.dequeue() ); }