/**
* Copyright (c) 2008 Greg Whalin
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the BSD license
*
* This library 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 a copy of the BSD License along with this
* library.
*
* @author Greg Whalin */
package com.yulong.memcached;
* Supports setting, adding, replacing, deleting compressed/uncompressed and
* serialized (can be stored as string if object is native class) objects to memcached.
*
* Now pulls SockIO objects from SockIOPool, which is a connection pool. The server failover
* has also been moved into the SockIOPool class.
* This pool needs to be initialized prior to the client working. See javadocs from SockIOPool.
*
* Some examples of use follow.
*
To create cache client object and set params:
*
* MemcachedClient mc = new MemcachedClient();
*
* // compression is enabled by default
* mc.setCompressEnable(true);
*
* // set compression threshhold to 4 KB (default: 15 KB)
* mc.setCompressThreshold(4096);
*
* // turn on storing primitive types as a string representation
* // Should not do this in most cases.
* mc.setPrimitiveAsString(true);
*
*
To store an object:
*
* MemcachedClient mc = new MemcachedClient();
* String key = "cacheKey1";
* Object value = SomeClass.getObject();
* mc.set(key, value);
*
*
To store an object using a custom server hashCode:
*
* MemcachedClient mc = new MemcachedClient();
* String key = "cacheKey1";
* Object value = SomeClass.getObject();
* Integer hash = new Integer(45);
* mc.set(key, value, hash);
*
* The set method shown above will always set the object in the cache.
* The add and replace methods do the same, but with a slight difference.
*
*
add -- will store the object only if the server does not have an entry for this key
*
replace -- will store the object only if the server already has an entry for this key
*
*
To delete a cache entry:
*
* MemcachedClient mc = new MemcachedClient();
* String key = "cacheKey1";
* mc.delete(key);
*
*
To delete a cache entry using a custom hash code:
*
* MemcachedClient mc = new MemcachedClient();
* String key = "cacheKey1";
* Integer hash = new Integer(45);
* mc.delete(key, hashCode);
*
*
To store a counter and then increment or decrement that counter:
*
* MemcachedClient mc = new MemcachedClient();
* String key = "counterKey";
* mc.storeCounter(key, new Integer(100));
* System.out.println("counter after adding 1: " mc.incr(key));
* System.out.println("counter after adding 5: " mc.incr(key, 5));
* System.out.println("counter after subtracting 4: " mc.decr(key, 4));
* System.out.println("counter after subtracting 1: " mc.decr(key));
*
*
To store a counter and then increment or decrement that counter with custom hash:
*
* MemcachedClient mc = new MemcachedClient();
* String key = "counterKey";
* Integer hash = new Integer(45);
* mc.storeCounter(key, new Integer(100), hash);
* System.out.println("counter after adding 1: " mc.incr(key, 1, hash));
* System.out.println("counter after adding 5: " mc.incr(key, 5, hash));
* System.out.println("counter after subtracting 4: " mc.decr(key, 4, hash));
* System.out.println("counter after subtracting 1: " mc.decr(key, 1, hash));
*
*
To retrieve an object from the cache:
*
* MemcachedClient mc = new MemcachedClient();
* String key = "key";
* Object value = mc.get(key);
*
*
To retrieve an object from the cache with custom hash:
*
* MemcachedClient mc = new MemcachedClient();
* String key = "key";
* Integer hash = new Integer(45);
* Object value = mc.get(key, hash);
*
*
To retrieve an multiple objects from the cache
*
* MemcachedClient mc = new MemcachedClient();
* String[] keys = { "key", "key1", "key2" };
* Map
*
To retrieve an multiple objects from the cache with custom hashing
*
* MemcachedClient mc = new MemcachedClient();
* String[] keys = { "key", "key1", "key2" };
* Integer[] hashes = { new Integer(45), new Integer(32), new Integer(44) };
* Map
*
To flush all items in server(s)
*
* MemcachedClient mc = new MemcachedClient();
* mc.flushAll();
*
*
To get stats from server(s)
*
* MemcachedClient mc = new MemcachedClient();
* Map stats = mc.stats();
*
*
* @author greg whalin * @author Richard 'toast' Russo * @author Kevin Burton * @author Robert Watts * @author Vin Chawla * @version 1.5
*/
@Deprecated
public class MemcachedClient {
// return codes
private static final String VALUE = "VALUE"; // start of value line from server
private static final String STATS = "STAT"; // start of stats line from server
private static final String ITEM = "ITEM"; // start of item line from server
private static final String DELETED = "DELETED"; // successful deletion
private static final String NOTFOUND = "NOT_FOUND"; // record not found for delete or incr/decr
private static final String STORED = "STORED"; // successful store of data
private static final String NOTSTORED = "NOT_STORED"; // data not stored
private static final String OK = "OK"; // success
private static final String END = "END"; // end of data from server
private static final String ERROR = "ERROR"; // invalid command name from client
private static final String CLIENT_ERROR = "CLIENT_ERROR"; // client error in input line - invalid protocol
private static final String SERVER_ERROR = "SERVER_ERROR"; // server error
private static final byte[] B_END = "END\r\n".getBytes();
private static final byte[] B_NOTFOUND = "NOT_FOUND\r\n".getBytes();
private static final byte[] B_DELETED = "DELETED\r\r".getBytes();
private static final byte[] B_STORED = "STORED\r\r".getBytes();
// default compression threshold
private static final int COMPRESS_THRESH = 30720;
// values for cache flags
public static final int MARKER_BYTE = 1;
public static final int MARKER_BOOLEAN = 8192;
public static final int MARKER_INTEGER = 4;
public static final int MARKER_LONG = 16384;
public static final int MARKER_CHARACTER = 16;
public static final int MARKER_STRING = 32;
public static final int MARKER_STRINGBUFFER = 64;
public static final int MARKER_FLOAT = 128;
public static final int MARKER_SHORT = 256;
public static final int MARKER_DOUBLE = 512;
public static final int MARKER_DATE = 1024;
public static final int MARKER_STRINGBUILDER = 2048;
public static final int MARKER_BYTEARR = 4096;
public static final int F_COMPRESSED = 2;
public static final int F_SERIALIZED = 8;
/**
* Creates a new instance of MemCachedClient.
*/
public MemcachedClient() {
init();
}
/**
* Creates a new instance of MemCachedClient
* accepting a passed in pool name.
*
* @param poolName name of SockIOPool
*/
public MemcachedClient( String poolName ) {
this.poolName = poolName;
init();
}
/**
* Creates a new instance of MemCacheClient but
* acceptes a passed in ClassLoader.
*
* @param classLoader ClassLoader object.
*/
public MemcachedClient( ClassLoader classLoader ) {
this.classLoader = classLoader;
init();
}
/**
* Creates a new instance of MemCacheClient but
* acceptes a passed in ClassLoader and a passed
* in ErrorHandler.
*
* @param classLoader ClassLoader object.
* @param errorHandler ErrorHandler object.
*/
public MemcachedClient( ClassLoader classLoader, ErrorHandler errorHandler ) {
this.classLoader = classLoader;
this.errorHandler = errorHandler;
init();
}
/**
* Creates a new instance of MemCacheClient but
* acceptes a passed in ClassLoader, ErrorHandler,
* and SockIOPool name.
*
* @param classLoader ClassLoader object.
* @param errorHandler ErrorHandler object.
* @param poolName SockIOPool name
*/
public MemcachedClient( ClassLoader classLoader, ErrorHandler errorHandler, String poolName ) {
this.classLoader = classLoader;
this.errorHandler = errorHandler;
this.poolName = poolName;
init();
}
/**
* Initializes client object to defaults.
*
* This enables compression and sets compression threshhold to 15 KB.
*/
private void init() {
this.sanitizeKeys = true;
this.primitiveAsString = false;
this.compressEnable = true;
this.compressThreshold = COMPRESS_THRESH;
this.defaultEncoding = "UTF-8";
this.poolName = ( this.poolName == null ) ? "default" : this.poolName;
// get a pool instance to work with for the life of this instance
this.pool = SockIOPool.getInstance( poolName );
}
/**
* Sets an optional ClassLoader to be used for
* serialization.
*
* @param classLoader
*/
public void setClassLoader( ClassLoader classLoader ) {
this.classLoader = classLoader;
}
/**
* Enables/disables sanitizing keys by URLEncoding.
*
* @param sanitizeKeys if true, then URLEncode all keys
*/
public void setSanitizeKeys( boolean sanitizeKeys ) {
this.sanitizeKeys = sanitizeKeys;
}
/**
* Enables storing primitive types as their String values.
*
* @param primitiveAsString if true, then store all primitives as their string value.
*/
public void setPrimitiveAsString( boolean primitiveAsString ) {
this.primitiveAsString = primitiveAsString;
}
/**
* Sets default String encoding when storing primitives as Strings.
* Default is UTF-8.
*
* @param defaultEncoding
*/
public void setDefaultEncoding( String defaultEncoding ) {
this.defaultEncoding = defaultEncoding;
}
/**
* Enable storing compressed data, provided it meets the threshold requirements.
*
* If enabled, data will be stored in compressed form if it is
* longer than the threshold length set with setCompressThreshold(int)
*
* The default is that compression is enabled.
*
* Even if compression is disabled, compressed data will be automatically
* decompressed.
*
* @param compressEnable true to enable compression, false to disable compression
*/
public void setCompressEnable( boolean compressEnable ) {
this.compressEnable = compressEnable;
}
/**
* Sets the required length for data to be considered for compression.
*
* If the length of the data to be stored is not equal or larger than this value, it will
* not be compressed.
*
* This defaults to 15 KB.
*
* @param compressThreshold required length of data to consider compression
*/
public void setCompressThreshold( long compressThreshold ) {
this.compressThreshold = compressThreshold;
}
/**
* Checks to see if key exists in cache.
*
* @param key the key to look for
* @return true if key found in cache, false if not (or if cache is down)
*/
public boolean keyExists( String key ) {
return ( this.get( key, null, true ) != null );
}
/**
* Deletes an object from cache given cache key.
*
* @param key the key to be removed
* @return true, if the data was deleted successfully
*/
public boolean delete( String key ) {
return delete( key, null, null );
}
/**
* Deletes an object from cache given cache key and expiration date.
*
* @param key the key to be removed
* @param expiry when to expire the record.
* @return true, if the data was deleted successfully
*/
public boolean delete( String key, Date expiry ) {
return delete( key, null, expiry );
}
/**
* Deletes an object from cache given cache key, a delete time, and an optional hashcode.
*
* The item is immediately made non retrievable.
* Keep in mind {@link #add(String, Object) add} and {@link #replace(String, Object) replace}
* will fail when used with the same key will fail, until the server reaches the
* specified time. However, {@link #set(String, Object) set} will succeed,
* and the new value will not be deleted.
*
* @param key the key to be removed
* @param hashCode if not null, then the int hashcode to use
* @param expiry when to expire the record.
* @return true, if the data was deleted successfully
*/
public boolean delete( String key, Integer hashCode, Date expiry ) {
if ( key == null ) {
log.error( "null value for key passed to delete()" );
return false;
}
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnDelete( this, e, key );
log.error( "failed to sanitize your key!", e );
return false;
}
// get SockIO obj from hash or from key
SockIOPool.SockIO sock = pool.getSock( key, hashCode );
// return false if unable to get SockIO obj
if ( sock == null ) {
if ( errorHandler != null )
errorHandler.handleErrorOnDelete( this, new IOException( "no socket to server available" ), key );
return false;
}
// if we get appropriate response back, then we return true
String line = sock.readLine();
if ( DELETED.equals( line ) ) {
if ( log.isInfoEnabled() )
log.info( "++++ deletion of key: " + key + " from cache was a success" );
// return sock to pool and bail here
sock.close();
sock = null;
return true;
}
else if ( NOTFOUND.equals( line ) ) {
if ( log.isInfoEnabled() )
log.info( "++++ deletion of key: " + key + " from cache failed as the key was not found" );
}
else {
log.error( "++++ error deleting key: " + key );
log.error( "++++ server response: " + line );
}
}
catch ( IOException e ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnDelete( this, e, key );
// exception thrown
log.error( "++++ exception thrown while writing bytes to server on delete" );
log.error( e.getMessage(), e );
/**
* Stores data on the server; only the key and the value are specified.
*
* @param key key to store data under
* @param value value to store
* @return true, if the data was successfully stored
*/
public boolean set( String key, Object value ) {
return set( "set", key, value, null, null, primitiveAsString );
}
/**
* Stores data on the server; only the key and the value are specified.
*
* @param key key to store data under
* @param value value to store
* @param hashCode if not null, then the int hashcode to use
* @return true, if the data was successfully stored
*/
public boolean set( String key, Object value, Integer hashCode ) {
return set( "set", key, value, null, hashCode, primitiveAsString );
}
/**
* Stores data on the server; the key, value, and an expiration time are specified.
*
* @param key key to store data under
* @param value value to store
* @param expiry when to expire the record
* @return true, if the data was successfully stored
*/
public boolean set( String key, Object value, Date expiry ) {
return set( "set", key, value, expiry, null, primitiveAsString );
}
/**
* Stores data on the server; the key, value, and an expiration time are specified.
*
* @param key key to store data under
* @param value value to store
* @param expiry when to expire the record
* @param hashCode if not null, then the int hashcode to use
* @return true, if the data was successfully stored
*/
public boolean set( String key, Object value, Date expiry, Integer hashCode ) {
return set( "set", key, value, expiry, hashCode, primitiveAsString );
}
/**
* Adds data to the server; only the key and the value are specified.
*
* @param key key to store data under
* @param value value to store
* @return true, if the data was successfully stored
*/
public boolean add( String key, Object value ) {
return set( "add", key, value, null, null, primitiveAsString );
}
/**
* Adds data to the server; the key, value, and an optional hashcode are passed in.
*
* @param key key to store data under
* @param value value to store
* @param hashCode if not null, then the int hashcode to use
* @return true, if the data was successfully stored
*/
public boolean add( String key, Object value, Integer hashCode ) {
return set( "add", key, value, null, hashCode, primitiveAsString );
}
/**
* Adds data to the server; the key, value, and an expiration time are specified.
*
* @param key key to store data under
* @param value value to store
* @param expiry when to expire the record
* @return true, if the data was successfully stored
*/
public boolean add( String key, Object value, Date expiry ) {
return set( "add", key, value, expiry, null, primitiveAsString );
}
/**
* Adds data to the server; the key, value, and an expiration time are specified.
*
* @param key key to store data under
* @param value value to store
* @param expiry when to expire the record
* @param hashCode if not null, then the int hashcode to use
* @return true, if the data was successfully stored
*/
public boolean add( String key, Object value, Date expiry, Integer hashCode ) {
return set( "add", key, value, expiry, hashCode, primitiveAsString );
}
/**
* Updates data on the server; only the key and the value are specified.
*
* @param key key to store data under
* @param value value to store
* @return true, if the data was successfully stored
*/
public boolean replace( String key, Object value ) {
return set( "replace", key, value, null, null, primitiveAsString );
}
/**
* Updates data on the server; only the key and the value and an optional hash are specified.
*
* @param key key to store data under
* @param value value to store
* @param hashCode if not null, then the int hashcode to use
* @return true, if the data was successfully stored
*/
public boolean replace( String key, Object value, Integer hashCode ) {
return set( "replace", key, value, null, hashCode, primitiveAsString );
}
/**
* Updates data on the server; the key, value, and an expiration time are specified.
*
* @param key key to store data under
* @param value value to store
* @param expiry when to expire the record
* @return true, if the data was successfully stored
*/
public boolean replace( String key, Object value, Date expiry ) {
return set( "replace", key, value, expiry, null, primitiveAsString );
}
/**
* Updates data on the server; the key, value, and an expiration time are specified.
*
* @param key key to store data under
* @param value value to store
* @param expiry when to expire the record
* @param hashCode if not null, then the int hashcode to use
* @return true, if the data was successfully stored
*/
public boolean replace( String key, Object value, Date expiry, Integer hashCode ) {
return set( "replace", key, value, expiry, hashCode, primitiveAsString );
}
/**
* Stores data to cache.
*
* If data does not already exist for this key on the server, or if the key is being
* deleted, the specified value will not be stored.
* The server will automatically delete the value when the expiration time has been reached.
*
* If compression is enabled, and the data is longer than the compression threshold
* the data will be stored in compressed form.
*
* As of the current release, all objects stored will use java serialization.
*
* @param cmdname action to take (set, add, replace)
* @param key key to store cache under
* @param value object to cache
* @param expiry expiration
* @param hashCode if not null, then the int hashcode to use
* @param asString store this object as a string?
* @return true/false indicating success
*/
private boolean set( String cmdname, String key, Object value, Date expiry, Integer hashCode, boolean asString ) {
if ( cmdname == null || cmdname.trim().equals( "" ) || key == null ) {
log.error( "key is null or cmd is null/empty for set()" );
return false;
}
if ( sock == null ) {
if ( errorHandler != null )
errorHandler.handleErrorOnSet( this, new IOException( "no socket to server available" ), key );
return false;
}
if ( expiry == null )
expiry = new Date(0);
// store flags
int flags = 0;
// byte array to hold data
byte[] val;
if ( NativeHandler.isHandled( value ) ) {
if ( asString ) {
// useful for sharing data between java and non-java
// and also for storing ints for the increment method
try {
if ( log.isInfoEnabled() )
log.info( "++++ storing data as a string for key: " + key + " for class: " + value.getClass().getName() );
val = value.toString().getBytes( defaultEncoding );
}
catch ( UnsupportedEncodingException ue ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnSet( this, ue, key );
log.error( "invalid encoding type used: " + defaultEncoding, ue );
sock.close();
sock = null;
return false;
}
}
else {
try {
if ( log.isInfoEnabled() )
log.info( "Storing with native handler..." );
flags |= NativeHandler.getMarkerFlag( value );
val = NativeHandler.encode( value );
}
catch ( Exception e ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnSet( this, e, key );
log.error( "Failed to native handle obj", e );
sock.close();
sock = null;
return false;
}
}
}
else {
// always serialize for non-primitive types
try {
if ( log.isInfoEnabled() )
log.info( "++++ serializing for key: " + key + " for class: " + value.getClass().getName() );
ByteArrayOutputStream bos = new ByteArrayOutputStream();
(new ObjectOutputStream( bos )).writeObject( value );
val = bos.toByteArray();
flags |= F_SERIALIZED;
}
catch ( IOException e ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnSet( this, e, key );
// if we fail to serialize, then
// we bail
log.error( "failed to serialize obj", e );
log.error( value.toString() );
// return socket to pool and bail
sock.close();
sock = null;
return false;
}
}
// now try to compress if we want to
// and if the length is over the threshold
if ( compressEnable && val.length > compressThreshold ) {
try {
if ( log.isInfoEnabled() ) {
log.info( "++++ trying to compress data" );
log.info( "++++ size prior to compression: " + val.length );
}
ByteArrayOutputStream bos = new ByteArrayOutputStream( val.length );
GZIPOutputStream gos = new GZIPOutputStream( bos );
gos.write( val, 0, val.length );
gos.finish();
gos.close();
// store it and set compression flag
val = bos.toByteArray();
flags |= F_COMPRESSED;
/**
* Store a counter to memcached given a key
*
* @param key cache key
* @param counter number to store
* @return true/false indicating success
*/
public boolean storeCounter( String key, long counter ) {
return set( "set", key, new Long( counter ), null, null, true );
}
/**
* Store a counter to memcached given a key
*
* @param key cache key
* @param counter number to store
* @return true/false indicating success
*/
public boolean storeCounter( String key, Long counter ) {
return set( "set", key, counter, null, null, true );
}
/**
* Store a counter to memcached given a key
*
* @param key cache key
* @param counter number to store
* @param hashCode if not null, then the int hashcode to use
* @return true/false indicating success
*/
public boolean storeCounter( String key, Long counter, Integer hashCode ) {
return set( "set", key, counter, null, hashCode, true );
}
/**
* Returns value in counter at given key as long.
*
* @param key cache ket
* @return counter value or -1 if not found
*/
public long getCounter( String key ) {
return getCounter( key, null );
}
/**
* Returns value in counter at given key as long.
*
* @param key cache ket
* @param hashCode if not null, then the int hashcode to use
* @return counter value or -1 if not found
*/
public long getCounter( String key, Integer hashCode ) {
if ( key == null ) {
log.error( "null key for getCounter()" );
return -1;
}
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, ex, key );
// not found or error getting out
if ( log.isInfoEnabled() )
log.info( String.format( "Failed to parse Long value for key: %s", key ) );
}
return counter;
}
/**
* Thread safe way to initialize and increment a counter.
*
* @param key key where the data is stored
* @return value of incrementer
*/
public long addOrIncr( String key ) {
return addOrIncr( key, 0, null );
}
/**
* Thread safe way to initialize and increment a counter.
*
* @param key key where the data is stored
* @param inc value to set or increment by
* @return value of incrementer
*/
public long addOrIncr( String key, long inc ) {
return addOrIncr( key, inc, null );
}
/**
* Thread safe way to initialize and increment a counter.
*
* @param key key where the data is stored
* @param inc value to set or increment by
* @param hashCode if not null, then the int hashcode to use
* @return value of incrementer
*/
public long addOrIncr( String key, long inc, Integer hashCode ) {
boolean ret = set( "add", key, new Long( inc ), null, hashCode, true );
if ( ret ) {
return inc;
}
else {
return incrdecr( "incr", key, inc, hashCode );
}
}
/**
* Thread safe way to initialize and decrement a counter.
*
* @param key key where the data is stored
* @return value of incrementer
*/
public long addOrDecr( String key ) {
return addOrDecr( key, 0, null );
}
/**
* Thread safe way to initialize and decrement a counter.
*
* @param key key where the data is stored
* @param inc value to set or increment by
* @return value of incrementer
*/
public long addOrDecr( String key, long inc ) {
return addOrDecr( key, inc, null );
}
/**
* Thread safe way to initialize and decrement a counter.
*
* @param key key where the data is stored
* @param inc value to set or increment by
* @param hashCode if not null, then the int hashcode to use
* @return value of incrementer
*/
public long addOrDecr( String key, long inc, Integer hashCode ) {
boolean ret = set( "add", key, new Long( inc ), null, hashCode, true );
if ( ret ) {
return inc;
}
else {
return incrdecr( "decr", key, inc, hashCode );
}
}
/**
* Increment the value at the specified key by 1, and then return it.
*
* @param key key where the data is stored
* @return -1, if the key is not found, the value after incrementing otherwise
*/
public long incr( String key ) {
return incrdecr( "incr", key, 1, null );
}
/**
* Increment the value at the specified key by passed in val.
*
* @param key key where the data is stored
* @param inc how much to increment by
* @return -1, if the key is not found, the value after incrementing otherwise
*/
public long incr( String key, long inc ) {
return incrdecr( "incr", key, inc, null );
}
/**
* Increment the value at the specified key by the specified increment, and then return it.
*
* @param key key where the data is stored
* @param inc how much to increment by
* @param hashCode if not null, then the int hashcode to use
* @return -1, if the key is not found, the value after incrementing otherwise
*/
public long incr( String key, long inc, Integer hashCode ) {
return incrdecr( "incr", key, inc, hashCode );
}
/**
* Decrement the value at the specified key by 1, and then return it.
*
* @param key key where the data is stored
* @return -1, if the key is not found, the value after incrementing otherwise
*/
public long decr( String key ) {
return incrdecr( "decr", key, 1, null );
}
/**
* Decrement the value at the specified key by passed in value, and then return it.
*
* @param key key where the data is stored
* @param inc how much to increment by
* @return -1, if the key is not found, the value after incrementing otherwise
*/
public long decr( String key, long inc ) {
return incrdecr( "decr", key, inc, null );
}
/**
* Decrement the value at the specified key by the specified increment, and then return it.
*
* @param key key where the data is stored
* @param inc how much to increment by
* @param hashCode if not null, then the int hashcode to use
* @return -1, if the key is not found, the value after incrementing otherwise
*/
public long decr( String key, long inc, Integer hashCode ) {
return incrdecr( "decr", key, inc, hashCode );
}
/**
* Increments/decrements the value at the specified key by inc.
*
* Note that the server uses a 32-bit unsigned integer, and checks for
* underflow. In the event of underflow, the result will be zero. Because
* Java lacks unsigned types, the value is returned as a 64-bit integer.
* The server will only decrement a value if it already exists;
* if a value is not found, -1 will be returned.
*
* @param cmdname increment/decrement
* @param key cache key
* @param inc amount to incr or decr
* @param hashCode if not null, then the int hashcode to use
* @return new value or -1 if not exist
*/
private long incrdecr( String cmdname, String key, long inc, Integer hashCode ) {
if ( key == null ) {
log.error( "null key for incrdecr()" );
return -1;
}
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
log.error( "failed to sanitize your key!", e );
return -1;
}
// get SockIO obj for given cache key
SockIOPool.SockIO sock = pool.getSock( key, hashCode );
if ( sock == null ) {
if ( errorHandler != null )
errorHandler.handleErrorOnSet( this, new IOException( "no socket to server available" ), key );
return -1;
}
// return sock to pool and return result
sock.close();
try {
return Long.parseLong( line );
}
catch ( Exception ex ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, ex, key );
log.error( String.format( "Failed to parse Long value for key: %s", key ) );
}
}
else if ( NOTFOUND.equals( line ) ) {
if ( log.isInfoEnabled() )
log.info( "++++ key not found to incr/decr for key: " + key );
}
else {
log.error( "++++ error incr/decr key: " + key );
log.error( "++++ server response: " + line );
}
}
catch ( IOException e ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
// exception thrown
log.error( "++++ exception thrown while writing bytes to server on incr/decr" );
log.error( e.getMessage(), e );
/**
* Retrieve a key from the server, using a specific hash.
*
* If the data was compressed or serialized when compressed, it will automatically
* be decompressed or serialized, as appropriate. (Inclusive or)
*
* Non-serialized data will be returned as a string, so explicit conversion to
* numeric types will be necessary, if desired
*
* @param key key where data is stored
* @return the object that was previously stored, or null if it was not previously stored
*/
public Object get( String key ) {
return get( key, null, false );
}
/**
* Retrieve a key from the server, using a specific hash.
*
* If the data was compressed or serialized when compressed, it will automatically
* be decompressed or serialized, as appropriate. (Inclusive or)
*
* Non-serialized data will be returned as a string, so explicit conversion to
* numeric types will be necessary, if desired
*
* @param key key where data is stored
* @param hashCode if not null, then the int hashcode to use
* @return the object that was previously stored, or null if it was not previously stored
*/
public Object get( String key, Integer hashCode ) {
return get( key, hashCode, false );
}
/**
* Retrieve a key from the server, using a specific hash.
*
* If the data was compressed or serialized when compressed, it will automatically
* be decompressed or serialized, as appropriate. (Inclusive or)
*
* Non-serialized data will be returned as a string, so explicit conversion to
* numeric types will be necessary, if desired
*
* @param key key where data is stored
* @param hashCode if not null, then the int hashcode to use
* @param asString if true, then return string val
* @return the object that was previously stored, or null if it was not previously stored
*/
public Object get( String key, Integer hashCode, boolean asString ) {
if ( key == null ) {
log.error( "key is null for get()" );
return null;
}
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
log.error( "failed to sanitize your key!", e );
return null;
}
// get SockIO obj using cache key
SockIOPool.SockIO sock = pool.getSock( key, hashCode );
if ( sock == null ) {
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, new IOException( "no socket to server available" ), key );
return null;
}
try {
String cmd = "get " + key + "\r\n";
if ( log.isDebugEnabled() )
log.debug("++++ memcache get command: " + cmd);
sock.write( cmd.getBytes() );
sock.flush();
// ready object
Object o = null;
while ( true ) {
String line = sock.readLine();
if ( log.isDebugEnabled() )
log.debug( "++++ line: " + line );
if ( line.startsWith( VALUE ) ) {
String[] info = line.split(" ");
int flag = Integer.parseInt( info[2] );
int length = Integer.parseInt( info[3] );
// read obj into buffer
byte[] buf = new byte[length];
sock.read( buf );
sock.clearEOL();
if ( (flag & F_COMPRESSED) == F_COMPRESSED ) {
try {
// read the input stream, and write to a byte array output stream since
// we have to read into a byte array, but we don't know how large it
// will need to be, and we don't want to resize it a bunch
GZIPInputStream gzi = new GZIPInputStream( new ByteArrayInputStream( buf ) );
ByteArrayOutputStream bos = new ByteArrayOutputStream( buf.length );
int count;
byte[] tmp = new byte[2048];
while ( (count = gzi.read(tmp)) != -1 ) {
bos.write( tmp, 0, count );
}
// store uncompressed back to buffer
buf = bos.toByteArray();
gzi.close();
}
catch ( IOException e ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
log.error( "++++ IOException thrown while trying to uncompress input stream for key: " + key + " -- " + e.getMessage() );
throw new NestedIOException( "++++ IOException thrown while trying to uncompress input stream for key: " + key, e );
}
}
// we can only take out serialized objects
if ( ( flag & F_SERIALIZED ) != F_SERIALIZED ) {
if ( primitiveAsString || asString ) {
// pulling out string value
if ( log.isInfoEnabled() )
log.info( "++++ retrieving object and stuffing into a string." );
o = new String( buf, defaultEncoding );
}
else {
// decoding object
try {
o = NativeHandler.decode( buf, flag );
}
catch ( Exception e ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
log.error( "++++ Exception thrown while trying to deserialize for key: " + key, e );
throw new NestedIOException( e );
}
}
}
else {
// deserialize if the data is serialized
ContextObjectInputStream ois =
new ContextObjectInputStream( new ByteArrayInputStream( buf ), classLoader );
try {
o = ois.readObject();
if ( log.isInfoEnabled() )
log.info( "++++ deserializing " + o.getClass() );
}
catch ( Exception e ) {
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
o = null;
log.error( "++++ Exception thrown while trying to deserialize for key: " + key + " -- " + e.getMessage() );
}
}
}
else if ( END.equals( line ) ) {
if ( log.isDebugEnabled() )
log.debug( "++++ finished reading from cache server" );
break;
}
}
/**
* Retrieve multiple objects from the memcache.
*
* This is recommended over repeated calls to {@link #get(String) get()}, since it
* is more efficient.
*
* @param keys String array of keys to retrieve
* @return Object array ordered in same order as key array containing results
*/
public Object[] getMultiArray( String[] keys ) {
return getMultiArray( keys, null, false );
}
/**
* Retrieve multiple objects from the memcache.
*
* This is recommended over repeated calls to {@link #get(String) get()}, since it
* is more efficient.
*
* @param keys String array of keys to retrieve
* @param hashCodes if not null, then the Integer array of hashCodes
* @return Object array ordered in same order as key array containing results
*/
public Object[] getMultiArray( String[] keys, Integer[] hashCodes ) {
return getMultiArray( keys, hashCodes, false );
}
/**
* Retrieve multiple objects from the memcache.
*
* This is recommended over repeated calls to {@link #get(String) get()}, since it
* is more efficient.
*
* @param keys String array of keys to retrieve
* @param hashCodes if not null, then the Integer array of hashCodes
* @param asString if true, retrieve string vals
* @return Object array ordered in same order as key array containing results
*/
public Object[] getMultiArray( String[] keys, Integer[] hashCodes, boolean asString ) {
Map data = getMulti( keys, hashCodes, asString );
if ( data == null )
return null;
Object[] res = new Object[ keys.length ];
for ( int i = 0; i < keys.length; i++ ) {
res[i] = data.get( keys[i] );
}
return res;
}
/**
* Retrieve multiple objects from the memcache.
*
* This is recommended over repeated calls to {@link #get(String) get()}, since it
* is more efficient.
*
* @param keys String array of keys to retrieve
* @return a hashmap with entries for each key is found by the server,
* keys that are not found are not entered into the hashmap, but attempting to
* retrieve them from the hashmap gives you null.
*/
public Map getMulti( String[] keys ) {
return getMulti( keys, null, false );
}
/**
* Retrieve multiple keys from the memcache.
*
* This is recommended over repeated calls to {@link #get(String) get()}, since it
* is more efficient.
*
* @param keys keys to retrieve
* @param hashCodes if not null, then the Integer array of hashCodes
* @return a hashmap with entries for each key is found by the server,
* keys that are not found are not entered into the hashmap, but attempting to
* retrieve them from the hashmap gives you null.
*/
public Map getMulti( String[] keys, Integer[] hashCodes ) {
return getMulti( keys, hashCodes, false );
}
/**
* Retrieve multiple keys from the memcache.
*
* This is recommended over repeated calls to {@link #get(String) get()}, since it
* is more efficient.
*
* @param keys keys to retrieve
* @param hashCodes if not null, then the Integer array of hashCodes
* @param asString if true then retrieve using String val
* @return a hashmap with entries for each key is found by the server,
* keys that are not found are not entered into the hashmap, but attempting to
* retrieve them from the hashmap gives you null.
*/
public Map getMulti( String[] keys, Integer[] hashCodes, boolean asString ) {
if ( keys == null || keys.length == 0 ) {
log.error( "missing keys for getMulti()" );
return null;
}
Map cmdMap =
new HashMap();
for ( int i = 0; i < keys.length; ++i ) {
String key = keys[i];
if ( key == null ) {
log.error( "null key, so skipping" );
continue;
}
Integer hash = null;
if ( hashCodes != null && hashCodes.length > i )
hash = hashCodes[ i ];
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
log.error( "failed to sanitize your key!", e );
continue;
}
// get SockIO obj from cache key
SockIOPool.SockIO sock = pool.getSock( cleanKey, hash );
if ( sock == null ) {
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, new IOException( "no socket to server available" ), key );
continue;
}
// store in map and list if not already
if ( !cmdMap.containsKey( sock.getHost() ) )
cmdMap.put( sock.getHost(), new StringBuilder( "get" ) );
// backfill missing keys w/ null value
if ( ! ret.containsKey( key ) )
ret.put( key, null );
}
if ( log.isDebugEnabled() )
log.debug( "++++ memcache: got back " + ret.size() + " results" );
return ret;
}
/**
* This method loads the data from cache into a Map.
*
* Pass a SockIO object which is ready to receive data and a HashMap
* to store the results.
*
* @param sock socket waiting to pass back data
* @param hm hashmap to store data into
* @param asString if true, and if we are using NativehHandler, return string val
* @throws IOException if io exception happens while reading from socket
*/
private void loadMulti( LineInputStream input, Map hm, boolean asString ) throws IOException {
while ( true ) {
String line = input.readLine();
if ( log.isDebugEnabled() )
log.debug( "++++ line: " + line );
if ( line.startsWith( VALUE ) ) {
String[] info = line.split(" ");
String key = info[1];
int flag = Integer.parseInt( info[2] );
int length = Integer.parseInt( info[3] );
// read obj into buffer
byte[] buf = new byte[length];
input.read( buf );
input.clearEOL();
// ready object
Object o;
// check for compression
if ( (flag & F_COMPRESSED) == F_COMPRESSED ) {
try {
// read the input stream, and write to a byte array output stream since
// we have to read into a byte array, but we don't know how large it
// will need to be, and we don't want to resize it a bunch
GZIPInputStream gzi = new GZIPInputStream( new ByteArrayInputStream( buf ) );
ByteArrayOutputStream bos = new ByteArrayOutputStream( buf.length );
int count;
byte[] tmp = new byte[2048];
while ( (count = gzi.read(tmp)) != -1 ) {
bos.write( tmp, 0, count );
}
// store uncompressed back to buffer
buf = bos.toByteArray();
gzi.close();
}
catch ( IOException e ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
log.error( "++++ IOException thrown while trying to uncompress input stream for key: " + key + " -- " + e.getMessage() );
throw new NestedIOException( "++++ IOException thrown while trying to uncompress input stream for key: " + key, e );
}
}
// we can only take out serialized objects
if ( ( flag & F_SERIALIZED ) != F_SERIALIZED ) {
if ( primitiveAsString || asString ) {
// pulling out string value
if ( log.isInfoEnabled() )
log.info( "++++ retrieving object and stuffing into a string." );
o = new String( buf, defaultEncoding );
}
else {
// decoding object
try {
o = NativeHandler.decode( buf, flag );
}
catch ( Exception e ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
log.error( "++++ Exception thrown while trying to deserialize for key: " + key + " -- " + e.getMessage() );
throw new NestedIOException( e );
}
}
}
else {
// deserialize if the data is serialized
ContextObjectInputStream ois =
new ContextObjectInputStream( new ByteArrayInputStream( buf ), classLoader );
try {
o = ois.readObject();
if ( log.isInfoEnabled() )
log.info( "++++ deserializing " + o.getClass() );
}
catch ( InvalidClassException e ) {
/* Errors de-serializing are to be expected in the case of a
* long running server that spans client restarts with updated
* classes.
*/
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
o = null;
log.error( "++++ InvalidClassException thrown while trying to deserialize for key: " + key + " -- " + e.getMessage() );
}
catch ( ClassNotFoundException e ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this, e, key );
o = null;
log.error( "++++ ClassNotFoundException thrown while trying to deserialize for key: " + key + " -- " + e.getMessage() );
}
}
// store the object into the cache
if ( o != null )
hm.put( key, o );
}
else if ( END.equals( line ) ) {
if ( log.isDebugEnabled() )
log.debug( "++++ finished reading from cache server" );
break;
}
}
}
/**
* Invalidates the entire cache.
*
* Will return true only if succeeds in clearing all servers.
*
* @return success true/false
*/
public boolean flushAll() {
return flushAll( null );
}
/**
* Invalidates the entire cache.
*
* Will return true only if succeeds in clearing all servers.
* If pass in null, then will try to flush all servers.
*
* @param servers optional array of host(s) to flush (host:port)
* @return success true/false
*/
public boolean flushAll( String[] servers ) {
// get SockIOPool instance
// return false if unable to get SockIO obj
if ( pool == null ) {
log.error( "++++ unable to get SockIOPool instance" );
return false;
}
// get all servers and iterate over them
servers = ( servers == null )
? pool.getServers()
: servers;
// if no servers, then return early
if ( servers == null || servers.length <= 0 ) {
log.error( "++++ no servers to flush" );
return false;
}
boolean success = true;
for ( int i = 0; i < servers.length; i++ ) {
SockIOPool.SockIO sock = pool.getConnection( servers[i] );
if ( sock == null ) {
log.error( "++++ unable to get connection to : " + servers[i] );
success = false;
if ( errorHandler != null )
errorHandler.handleErrorOnFlush( this, new IOException( "no socket to server available" ) );
continue;
}
// if we get appropriate response back, then we return true
String line = sock.readLine();
success = ( OK.equals( line ) )
? success && true
: false;
}
catch ( IOException e ) {
// if we have an errorHandler, use its hook
if ( errorHandler != null )
errorHandler.handleErrorOnFlush( this, e );
// exception thrown
log.error( "++++ exception thrown while writing bytes to server on flushAll" );
log.error( e.getMessage(), e );
/**
* Retrieves stats for all servers.
*
* Returns a map keyed on the servername.
* The value is another map which contains stats
* with stat name as key and value as value.
*
* @return Stats map
*/
public Map stats() {
return stats( null );
}
/**
* Retrieves stats for passed in servers (or all servers).
*
* Returns a map keyed on the servername.
* The value is another map which contains stats
* with stat name as key and value as value.
*
* @param servers string array of servers to retrieve stats from, or all if this is null
* @return Stats map
*/
public Map stats( String[] servers ) {
return stats( servers, "stats\r\n", STATS );
}
/**
* Retrieves stats items for all servers.
*
* Returns a map keyed on the servername.
* The value is another map which contains item stats
* with itemname:number:field as key and value as value.
*
* @return Stats map
*/
public Map statsItems() {
return statsItems( null );
}
/**
* Retrieves stats for passed in servers (or all servers).
*
* Returns a map keyed on the servername.
* The value is another map which contains item stats
* with itemname:number:field as key and value as value.
*
* @param servers string array of servers to retrieve stats from, or all if this is null
* @return Stats map
*/
public Map statsItems( String[] servers ) {
return stats( servers, "stats items\r\n", STATS );
}
/**
* Retrieves stats items for all servers.
*
* Returns a map keyed on the servername.
* The value is another map which contains slabs stats
* with slabnumber:field as key and value as value.
*
* @return Stats map
*/
public Map statsSlabs() {
return statsSlabs( null );
}
/**
* Retrieves stats for passed in servers (or all servers).
*
* Returns a map keyed on the servername.
* The value is another map which contains slabs stats
* with slabnumber:field as key and value as value.
*
* @param servers string array of servers to retrieve stats from, or all if this is null
* @return Stats map
*/
public Map statsSlabs( String[] servers ) {
return stats( servers, "stats slabs\r\n", STATS );
}
/**
* Retrieves items cachedump for all servers.
*
* Returns a map keyed on the servername.
* The value is another map which contains cachedump stats
* with the cachekey as key and byte size and unix timestamp as value.
*
* @param slabNumber the item number of the cache dump
* @return Stats map
*/
public Map statsCacheDump( int slabNumber, int limit ) {
return statsCacheDump( null, slabNumber, limit );
}
/**
* Retrieves stats for passed in servers (or all servers).
*
* Returns a map keyed on the servername.
* The value is another map which contains cachedump stats
* with the cachekey as key and byte size and unix timestamp as value.
*
* @param servers string array of servers to retrieve stats from, or all if this is null
* @param slabNumber the item number of the cache dump
* @return Stats map
*/
public Map statsCacheDump( String[] servers, int slabNumber, int limit ) {
return stats( servers, String.format( "stats cachedump %d %d\r\n", slabNumber, limit ), ITEM );
}
// get all servers and iterate over them
servers = (servers == null)
? pool.getServers()
: servers;
// if no servers, then return early
if ( servers == null || servers.length <= 0 ) {
log.error( "++++ no servers to check stats" );
return null;
}
// array of stats Maps
Map statsMaps =
new HashMap();
for ( int i = 0; i < servers.length; i++ ) {
SockIOPool.SockIO sock = pool.getConnection( servers[i] );
if ( sock == null ) {
log.error( "++++ unable to get connection to : " + servers[i] );
if ( errorHandler != null )
errorHandler.handleErrorOnStats( this, new IOException( "no socket to server available" ) );
continue;
}
stats.put( key, value );
}
else if ( END.equals( line ) ) {
// finish when we get end from server
if ( log.isDebugEnabled() )
log.debug( "++++ finished reading from cache server" );
break;
}
else if ( line.startsWith( ERROR ) || line.startsWith( CLIENT_ERROR ) || line.startsWith( SERVER_ERROR ) ) {
log.error( "++++ failed to query stats" );
log.error( "++++ server response: " + line );
break;
}
protected final class NIOLoader {
protected Selector selector;
protected int numConns = 0;
protected MemcachedClient mc;
protected Connection[] conns;
public NIOLoader( MemcachedClient mc ) {
this.mc = mc;
}
private final class Connection {
public List incoming = new ArrayList();
public ByteBuffer outgoing;
public SockIOPool.SockIO sock;
public SocketChannel channel;
private boolean isDone = false;
public Connection( SockIOPool.SockIO sock, StringBuilder request ) throws IOException {
if ( log.isDebugEnabled() )
log.debug( "setting up connection to "+sock.getHost() );
channel = sock.getChannel();
if ( channel == null )
throw new IOException( "dead connection to: " + sock.getHost() );
channel.configureBlocking( false );
channel.register( selector, SelectionKey.OP_WRITE, this );
}
public void close() {
try {
if ( isDone ) {
// turn off non-blocking IO and return to pool
if ( log.isDebugEnabled() )
log.debug( "++++ gracefully closing connection to "+sock.getHost() );
public boolean isDone() {
// if we know we're done, just say so
if ( isDone )
return true;
// else find out the hard way
int strPos = B_END.length-1;
int bi = incoming.size() - 1;
while ( bi >= 0 && strPos >= 0 ) {
ByteBuffer buf = incoming.get( bi );
int pos = buf.position()-1;
while ( pos >= 0 && strPos >= 0 ) {
if ( buf.get( pos-- ) != B_END[strPos--] )
return false;
}
bi--;
}
isDone = strPos < 0;
return isDone;
}
public ByteBuffer getBuffer() {
int last = incoming.size()-1;
if ( last >= 0 && incoming.get( last ).hasRemaining() ) {
return incoming.get( last );
}
else {
ByteBuffer newBuf = ByteBuffer.allocate( 8192 );
incoming.add( newBuf );
return newBuf;
}
}
public String toString() {
return "Connection to " + sock.getHost() + " with " + incoming.size() + " bufs; done is " + isDone;
}
}
public void doMulti( boolean asString, Map sockKeys, String[] keys, Map ret ) {
long timeRemaining = 0;
try {
selector = Selector.open();
// get the sockets, flip them to non-blocking, and set up data
// structures
conns = new Connection[sockKeys.keySet().size()];
numConns = 0;
for ( Iterator i = sockKeys.keySet().iterator(); i.hasNext(); ) {
// get SockIO obj from hostname
String host = i.next();
if ( sock == null ) {
if ( errorHandler != null )
errorHandler.handleErrorOnGet( this.mc, new IOException( "no socket to server available" ), keys );
return;
}
conns[numConns++] = new Connection( sock, sockKeys.get( host ) );
}
// the main select loop; ends when
// 1) we've received data from all the servers, or
// 2) we time out
long startTime = System.currentTimeMillis();
long timeout = pool.getMaxBusy();
timeRemaining = timeout;
while ( numConns > 0 && timeRemaining > 0 ) {
int n = selector.select( Math.min( timeout, 5000 ) );
if ( n > 0 ) {
// we've got some activity; handle it
Iterator it = selector.selectedKeys().iterator();
while ( it.hasNext() ) {
SelectionKey key = it.next();
it.remove();
handleKey( key );
}
}
else {
// timeout likely... better check
// TODO: This seems like a problem area that we need to figure out how to handle.
log.error( "selector timed out waiting for activity" );
}
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.view.View.getImportantForAccessibility()' on a null object reference
出现以上异常.然后就在baidu上
cmd命令打jar是如下实现:
在运行里输入cmd,利用cmd命令进入到本地的工作盘符。(如我的是D盘下的文件有此路径 D:\workspace\prpall\WEB-INF\classes)
现在是想把D:\workspace\prpall\WEB-INF\classes路径下所有的文件打包成prpall.jar。然后继续如下操作:
cd D: 回车
cd workspace/prpal
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml&q
Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing all ones and return its area.
public class Solution {
public int maximalRectangle(char[][] matrix)
随着RESTful Web Service的流行,测试对外的Service是否满足期望也变的必要的。从Spring 3.2开始Spring了Spring Web测试框架,如果版本低于3.2,请使用spring-test-mvc项目(合并到spring3.2中了)。
Spring MVC测试框架提供了对服务器端和客户端(基于RestTemplate的客户端)提供了支持。
&nbs
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
/*you'll also need to set UIViewControllerBasedStatusBarAppearance to NO in the plist file if you use this method
英文资料:
Thread Dump and Concurrency Locks
Thread dumps are very useful for diagnosing synchronization related problems such as deadlocks on object monitors. Ctrl-\ on Solaris/Linux or Ctrl-B