com.mchange.v2.lock.ExactReentrantSharedUseExclusiveUseLock Maven / Gradle / Ivy
/*
* Distributed as part of mchange-commons-java 0.2.6.3
*
* Copyright (C) 2013 Machinery For Change, Inc.
*
* Author: Steve Waldman
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of EITHER:
*
* 1) The GNU Lesser General Public License (LGPL), version 2.1, as
* published by the Free Software Foundation
*
* OR
*
* 2) The Eclipse Public License (EPL), version 1.0
*
* You may choose which license to accept if you wish to redistribute
* or modify this work. You may offer derivatives of this work
* under the license you have chosen, or you may provide the same
* choice of license which you have been offered here.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* You should have received copies of both LGPL v2.1 and EPL v1.0
* along with this software; see the files LICENSE-EPL and LICENSE-LGPL.
* If not, the text of these licenses are currently available at
*
* LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php
*
*/
package com.mchange.v2.lock;
import java.util.*;
/**
* Fully reentrant. We could still separate the shared and exclusive wait() sets for greater efficiency
* under circumstances of high contention, but it would require extra synchronizations even under low
* contention, so we'll leave this as is, and maybe write a different class that presumes large wait
* sets.
*
* Because this class is reentrant, lock relinquishes can't be idempotent -- we have to keep track of
* precisely how many times the lock was acquired and then release. Clients therefore have to be very
* careful that every acquisition is matched by exactly one relinquish. The way to do this is to acquire
* locks immediately before a try lock, and putting the relinquish in the finally block. One
* should not attempt to acquire the locks within the try block, because if the lock acquisition is
* interrupted, the lock relinquish in the finally block will run without the lock acquisition having
* succeeded.
*/
public class ExactReentrantSharedUseExclusiveUseLock implements SharedUseExclusiveUseLock
{
Set waitingShared = new HashSet(); //can't reenter this set, 'cuz once a thread enters, it waits
List activeShared = new LinkedList(); //can reenter this, so we are duplicate-holding Collection
Set waitingExclusive = new HashSet();
Thread activeExclusive = null;
int exclusive_shared_reentries = 0;
int exclusive_exclusive_reentries = 0;
String name;
public ExactReentrantSharedUseExclusiveUseLock( String name )
{ this.name = name; }
public ExactReentrantSharedUseExclusiveUseLock()
{ this(null); }
void status( String afterMethod )
{
System.err.println( this + " -- after " + afterMethod );
System.err.println( "waitingShared: " + waitingShared );
System.err.println( "activeShared: " + activeShared );
System.err.println( "waitingExclusive: " + waitingExclusive );
System.err.println( "activeExclusive: " + activeExclusive );
System.err.println( "exclusive_shared_reentries: " + exclusive_shared_reentries );
System.err.println( "exclusive_exclusive_reentries: " + exclusive_exclusive_reentries );
System.err.println( " ---- " );
System.err.println( );
}
public synchronized void acquireShared() throws InterruptedException
{
Thread t = Thread.currentThread();
if ( t == activeExclusive )
++exclusive_shared_reentries;
else
{
try
{
waitingShared.add( t );
while (! okayForShared())
this.wait();
activeShared.add( t );
}
finally
{
waitingShared.remove( t );
}
}
//status("acquireShared()");
}
public synchronized void relinquishShared()
{
Thread t = Thread.currentThread();
if ( t == activeExclusive )
{
--exclusive_shared_reentries;
if ( exclusive_shared_reentries < 0 )
throw new IllegalStateException( t + " relinquished a shared lock (reentrant on exclusive) it did not hold!" );
}
else
{
// note this covers the case where a thread has an exclusive lock
// and some arbitrary thread tries to relinquish a lock it did not
// hold.
boolean check = activeShared.remove( t );
if (! check)
throw new IllegalStateException( t + " relinquished a shared lock it did not hold!" );
this.notifyAll();
}
//status("relinquishShared()");
}
public synchronized void acquireExclusive() throws InterruptedException
{
Thread t = Thread.currentThread();
if ( t == activeExclusive )
++exclusive_exclusive_reentries;
else
{
try
{
waitingExclusive.add ( t );
while (! okayForExclusive( t ))
this.wait();
activeExclusive = t;
}
finally
{
waitingExclusive.remove( t );
}
}
//status("acquireExclusive()");
}
public synchronized void relinquishExclusive()
{
Thread t = Thread.currentThread();
if (t != activeExclusive)
throw new IllegalStateException( t + " relinquished an exclusive lock it did not hold!" );
else if (exclusive_exclusive_reentries > 0)
--exclusive_exclusive_reentries;
else
{
if ( exclusive_shared_reentries != 0 )
throw new IllegalStateException( t + " relinquished an exclusive lock while it had reentered but not yet relinquished shared lock acquisitions!" );
activeExclusive = null;
this.notifyAll();
}
//status("relinquishExcusive()");
}
private boolean okayForShared()
{ return ( activeExclusive == null && waitingExclusive.size() == 0 ); }
private boolean okayForExclusive( Thread t )
{
// originally simple...
// return ( activeShared.size() == 0 && activeExclusive == null );
//
// modified to allow for exclusive reentry when unique shared lock owner
int active_shared_sz = activeShared.size();
if ( active_shared_sz == 0 )
return (activeExclusive == null); //that case where (activeExclusive == t) is special-cased above
else if (active_shared_sz == 1)
return (activeShared.get(0) == t);
else
{
Set activeSharedNoDups = new HashSet( activeShared );
return (activeSharedNoDups.size() == 1 && activeSharedNoDups.contains( t ));
}
}
public String toString()
{ return super.toString() + " [name=" + name + ']'; }
}