com.bigdata.counters.striped.StripedCounters Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Apr 29, 2010
*/
package com.bigdata.counters.striped;
import java.lang.reflect.Constructor;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.system.SystemUtil;
import com.bigdata.counters.CounterSet;
/**
* Abstract base class and template for striped counters designed for high
* concurrency with eventual consistency and approximate read back without CAS
* contention.
*
* Each instance of this class in use by the application is backed by an array
* of N instances protected by striped locks. You {@link #acquire()} an instance
* and then directly manipulate the value of the counters on that instance. When
* you {@link #release()} the instance, the counters MAY be published to the
* outer instance visible to your application using
* {@link #add(StripedCounters)}. This is similar to an eventually consistent
* transactional model, except that there are no constraints on the validity of
* the updates imposed by this scheme.
*
* If reads will be made against the shared instance without holding a lock,
* then the individual counters should be volatile fields, {@link AtomicLong},
* or the like so that updates will be visible each time
* {@link #add(StripedCounters)} is invoked by {@link #release()} will be
* published in a timely manner.
*
* @author Bryan Thompson
*
* @todo The problem with this approach is that we can not decide that some
* counters should be published immediately while others should be
* published only every M touches [this might be something which can be
* hacked for specific counters using an incFoo() or addFoo() method for
* that counter which always updates the value on the parent if invoked on
* a striped counter.]
*/
public class StripedCounters> {
/** #of releases before we post the counters to the parent. */
protected /*private*/ int batchSize;
/**
* Decremented by {@link #release()} - when ZERO (0) we post the counters to
* the {@link #parent} and reset to the {@link #batchSize}
*
*/
protected /*private*/ int n;
/**
* The child counters (these are the stripes).
*/
private final StripedCounters>[] a;
protected /*private*/ final ReentrantLock[] locks;
protected /*private*/ T parent;
/**
* Return the stripe to be used by the caller's thread.
*/
private final int threadHash() {
return Math.abs(Thread.currentThread().hashCode() % a.length);
}
/**
* Cached value of {@link #threadHash()} as computed by the parent in
* {@link #acquire()}.
*/
private int threadHash;
/**
* Acquire a "stripe" counters object. The selected stripe is based on
* {@link Thread#hashCode()}. The chances of a contention with a concurrent
* thread are (1.0/nstripes)
.
*
* Note: When using this class you MUST be careful to avoid constructions
* which could lead to lock ordering problems. If there are other locks
* help, then each {@link #acquire()} MAY result in a deadlock with an
* {@link #acquire()} by another thread depending on which stripe is
* selected for each thread. Such deadlocks are obviously path dependent and
* difficult to detect.
*/
@SuppressWarnings("unchecked")
final public T acquire() {
if (a == null)
return (T) this;
final int i = threadHash();
a[i].threadHash = i; // save for the child to use in release()
locks[i].lock();
return (T) a[i];
}
/**
* Release the counters.
*/
@SuppressWarnings("unchecked")
final public void release() {
if (parent == null)
return;
if (--n == 0) {
n = batchSize;
// lock for update on the parent.
synchronized (parent) {
parent.add((T) this);
}
// zero values on the child.
clear();
}
parent.locks[threadHash].unlock();
}
/**
* Required public zero argument constructor provides no concurrency
* control. Instances created with this constructor are used (internally) in
* a variety of contexts where striped locks are not appropriate and may be
* used by the application as well if striped locks are not necessary.
*/
public StripedCounters() {
a = null;
locks = null;
parent = null;
batchSize = n = 1;
}
/**
* Create a striped counters object using numProcessors*2
* stripes and the specified batchSize.
*
* @param batchSize
* The values on the instance stripes will be published to the
* outer instance every batchSize {@link #release()}s.
*/
public StripedCounters(final int batchSize) {
this(SystemUtil.numProcessors() * 2, batchSize);
}
/**
* Create a striped counters object.
*
* @param nstripes
* The #of stripes. Each stripe is protected by a lock. The
* stripe is selected by {@link #acquire()} based on
* {@link Thread#hashCode()}. The chances of a contention with a
* concurrent thread are (1.0/nstripes)
.
* @param batchSize
* The values on the instance stripes will be published to the
* outer instance every batchSize {@link #release()}s.
*/
public StripedCounters(final int nstripes, final int batchSize) {
if (nstripes < 1)
throw new IllegalArgumentException();
if (batchSize < 1)
throw new IllegalArgumentException();
a = new StripedCounters[nstripes];
locks = new ReentrantLock[nstripes];
for (int i = 0; i < nstripes; i++) {
final T t = newStripedCounters();
t.parent = (T) this;
t.batchSize = t.n = batchSize;
a[i] = t;
locks[i] = new ReentrantLock();
}
this.parent = null;
this.batchSize = n = batchSize;
}
// /** One of a set of striped counters accessed used {@link #acquire()}. */
// protected StripedCounters(final StripedCounters parent, final int batchSize) {
// a = null;
// locks = null;
// this.parent = parent;
// this.batchSize = n = batchSize;
// }
/**
* Create an instance of the same class using the public zero-argument
* constructor for that class.
*
* @return The new instance.
*/
@SuppressWarnings("unchecked")
private T newStripedCounters() {
try {
final Constructor> ctor = getClass().getConstructor(
new Class[] {});
return (T) ctor.newInstance(new Object[] {});
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
/*
* Methods which must be extended by derived classes.
*/
/**
* Adds counters to the current counters.
*
* @param o The other counters.
*/
public void add(final T o) {
// add together the given counters.
// nreads += o.nreads;
// ndiskRead += o.ndiskRead;
}
/**
* Returns a new {@link StripedCounters} containing the current counter
* values minus the given counter values.
*
* @param o
* The other counters.
*
* @return A new set of counters.
*/
public T subtract(final T o) {
// make a copy of the current counters.
final T t = newStripedCounters();
t.add((T) this);
// subtract out the given counters.
// t.nreads -= o.nreads;
// t.ndiskRead -= o.ndiskRead;
return t;
}
/**
* Clear the counter values back to zero.
*/
public void clear() {
}
/**
* Return a new, empty {@link CounterSet}. This method must be extended
* to attach the various performance counters to that {@link CounterSet}.
*/
public CounterSet getCounters() {
return new CounterSet();
}
/**
* Return a serialization of the performance counters.
*/
public String toString() {
return getCounters().toString();
}
}