2010년 9월 26일 일요일

Cache

 

 

 

package org.seasar.caching.interceptor;

import java.io.Serializable;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.framework.aop.interceptors.AbstractInterceptor;

/**
 * メソッドに対する呼び出しをキャッシュするInterceptor.
 *
 * Daoに対して適用する場合、以下のように用いる.
 * <ul>
 *  <li>getやfindなどの取得系メソッドに CallCacheInterceptorをセットする
 *  <li>update,insert,delete,setなどの更新系メソッドに CallCachePurgeInterceptorをセットする
 *  <li>両Interceptorが同じキャッシュ領域を見に行くように コンストラクタの第二引数を等しいキー文字列にする
 *  <li>おなじく同一のCacheManagerを参照にいくように第一引数を等しいものにする
 * </ul>
 *
 * 特に以下の点に注意する必要がある
 * <ul>
 *  <li><b>インターセプト先のインスタンスはキャッシュ領域に対してsingletonでなくてはならない.</b>
 *  キャッシュに格納されているキーに、対象インスタンスを識別する機能がなく、メソッドのシグネチャと引数
 *  のセットのみをキーとしているため.
 *   <li>メソッドの引数は全てSerializableでない場合はキャッシュしない.
 *   <li>メソッドの戻り値型がSerializableでない場合はキャッシュしない.
 *   <li>例外がスローされた場合はキャッシュしない.
 * </ul>
 * 
 * @author TANIGUCHI Hikaru
 */
public class CallCacheInterceptor extends AbstractInterceptor {
    private static final Log logger = LogFactory.getLog(CallCacheInterceptor.class);
   
    private final Cache cache;
    private final CacheManager cacheManager;
    private final String cacheName;
   
    /**
     * constructor
     *
     * @param cacheManager ehCacheのキャッシュマネージャ
     * @param cacheName キャッシュ名称(キャッシュマネージャに対するキー)
     * @throws CacheException
     */
    public CallCacheInterceptor(CacheManager cacheManager, String cacheName) throws CacheException {
        this.cacheManager = cacheManager;
        this.cacheName = cacheName;
        if (!cacheManager.cacheExists(cacheName)) {
            cacheManager.addCache(cacheName);
        }
        this.cache = cacheManager.getCache(cacheName);
    }
   
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 全ての引数がSerializableでないとキャッシュの問い合わせ・追加に意味はない
        if (!isAllArgumentsSerializable(invocation) || !isReturnTypeSerializable(invocation)) {
            return invocation.proceed();
        }
       
        // 引数は全てSerializableなので CallDescriptionが生成可能。キャッシュ問い合わせ
        CallDescription description = new CallDescription(invocation);
        Element element = cache.get(description);
        if (element != null) {
            Serializable originalResult = element.getValue();
           
            return SerializationUtils.clone(originalResult);
        } else {
            Object result = invocation.proceed(); // 例外発生時は上位へそのままスロー、キャッシュされない
           
            Element insertElement = new Element(description, (Serializable) result);
            cache.put(insertElement);
           
            return SerializationUtils.clone((Serializable)result);
        }
    }

}package org.seasar.caching.interceptor;

import java.io.Serializable;
import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

public class CallDescription implements Serializable {
    private int targetObject;
    private Class declaredClass;
    private String methodName;
    private Class[] methodArguments;
    private Object[] argument;
   
    public CallDescription( MethodInvocation invocation ) {
        Method method = invocation.getMethod();
        argument = invocation.getArguments();
        targetObject = System.identityHashCode(invocation.getThis());
        declaredClass = method.getDeclaringClass();
        methodName = method.getName();
        methodArguments = method.getParameterTypes();
    }

    /**
     * @see java.lang.Object#equals(Object)
     */
    public boolean equals(Object object) {
        if (!(object instanceof CallDescription)) {
            return false;
        }
        CallDescription rhs = (CallDescription) object;
        return new EqualsBuilder().append(this.methodArguments, rhs.methodArguments).append(
                this.declaredClass, rhs.declaredClass).append(this.argument, rhs.argument).append(
                this.methodName, rhs.methodName).append(targetObject, rhs.targetObject).isEquals();
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
        return new HashCodeBuilder(661437325, -495862237).append(
                this.methodArguments).append(this.declaredClass).append(this.argument)
                .append(this.methodName).append(targetObject).toHashCode();
    }

    /**
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return new ToStringBuilder(this).append(this.methodName).append(this.methodArguments)
                .append(this.argument).append(this.targetObject).toString();
    }
}

 

2010년 5월 31일 월요일

デッドロックの確認

http://www.fumikichan.net/prog/Java/se030201.jsp

 

デッドロックの確認


1.デッドロックとは

 マルチスレッドを行うプログラムでは、複数のスレッドがお互いにロックの解放を待ち合い続けることによりシステムWait状態になる場合があります。 この現象をデッドロック(deadlock)といい、正常なプログラミングを行っていても、特定のオブジェクトのアクセスする順序により発生してしまいます。 デッドロックが発生しないようにするには、この特定のアクセス順序のルールを検討する必要があります。

 下の図でデッドロックが発生するまでの流れを説明します。

  • オブジェクトAとBを持つクラスから、お互いのオブジェクトを参照するスレッド1とスレッド2を生成します。
  • スレッド1はオブジェクトAのロックを取得し、オブジェクトBのロック解除を待っています。
  • スレッド2はオブジェクトBのロックを取得し、オブジェクトAのロック解除を待っています。
  • どちらのスレッドも互いにロックしたオブジェクトの解除を待つことになり永遠にWait状態になります。
デッドロックの流れ
 

2.プログラムの仕様

 では上記のデッドロックを実際にコーディングすることにより確認してみることにします。 プログラムは1クラスでも可能ですが、なるべく「デッドロックの流れ」の図に合うように3つのソースで5クラスに分けて作成します。

 簡単なプログラムの概要を下記に示します。

  • オブジェクトAとBはクラスAクラスBとし、それぞれ画面表示の2つのメソッドを持ちます。

  • オブジェクトAとBの実体はメインクラス(DeadlockTest)上にあり、他のクラスに参照設定を渡します。

  • メインクラス(DeadlockTest)からThreadAクラスのスレッドとThreadBクラスのスレッドを生成し、 両方のスレッドが終了したら終了メッセージを表示します。(デッドロックが発生した場合は表示されません。)

  • ThreadAクラスはオブジェクトAのロックを取得して、クラスAのメソッドを実行し、その後オブジェクトBのロックを取得して、クラスBのメソッドを実行します。

  • ThreadBクラスはオブジェクトBのロックを取得して、クラスBのメソッドを実行し、その後オブジェクトAのロックを取得して、クラスAのメソッドを実行します。

  • どちらのスレッドクラスもメソッド呼び出し処理をスレッド処理(run)の中で複数回(100回)行います。

なんとなく気になったので、Singleton を破壊するための TIPS

http://d.hatena.ne.jp/SiroKuro/20080403/1207236878

 

なんとなく気になったので、Singleton を破壊するための TIPS を余談として提示してみます。

結局クラスが一意であることを利用してクラスを1対1に結びついた単一のオブジェクトを作ってるだけなんだから

証明を書くには余白が足りない - 西尾泰和のはてなダイアリー

結局、クラスが一意じゃなければ、Singleton は破壊できちゃうんですよね。

今回破壊する Singleton はこういうクラスです。いたってシンプル

public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return instance; }
}

これを、こういうコードからロードしてみちゃったりします。だいぶ乱暴なことをしますね。

import java.io.*;
class test {
    public static void main(String[] args) throws Exception {
        System.out.println(newInstance() == newInstance());
    }
    private static Object newInstance() throws Exception {
        File file = new File("Singleton.class");
        final byte[] data = new byte[(int)file.length()];
        new FileInputStream(file).read(data, 0, data.length);
        return new ClassLoader() {
            public Class<?> destroySingleton() {
                return defineClass("Singleton", data, 0, data.length);
            }
        }.destroySingleton().getMethod("getInstance").invoke(null);
    }
}

結果は "false"

defineClass のたびに、新しい Singleton クラスが返ってきます。

素人にはお勧めできない。

微妙にクラスが一意という条件は満たしていますが、1つのクラスファイルから複数個の Class をロードできる、という例でした。

追記

上記コード

-    private static Object newInstance() throws Exception {
+    private static Singleton newInstance() throws Exception {

-        return new ClassLoader() {
+        return (Singleton) new ClassLoader() {

のように変更すると、もちろんコンパイルは通るんですが、実行すると

Exception in thread "main" java.lang.ClassCastException: Singleton cannot be cast to Singleton
        at test.newInstance(test.java:10)
        at test.main(test.java:4)

という、世にも奇妙な例外を吐き出してきます。

やっぱり素人にはお勧めできない。

Singleton オブジェクトをシリアル化したいときには

シリアル化したい場合は

http://d.hatena.ne.jp/SiroKuro/20080403/1207237637#c1207267140

ということで、Singleton オブジェクトシリアル化したいときには、こういう風にします。

import java.io.*;
public class Singleton implements Serializable {
    private static final Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return instance; }
    public Object readResolve() throws ObjectStreamException { return instance; }
}

Serializable くっつけて、readResolve という名前のメソッドを用意します。これで ObjectInputStream#readObject で返ってくるオブジェクトが、常に Singleton#instance になるという仕掛けです。

ということで Singleton 付けても readResolve を適切に実装すれば問題無い、という tips です。

memo(thread deadlock 調査)

http://blog.naver.com/leedc111/40034194835

 

 

http://www.nminoru.jp/~nminoru/java/jrockit70.html

http://otndnld.oracle.co.jp/document/products/jrockit/releases/R27/relnotes/r27_notes.html#wp1009832

スピンロックの使用
http://docs.sun.com/app/docs/doc/819-0390/ggecq?l=ja&a=view

http://download.oracle.com/docs/cd/E13188_01/jrockit/docs142/userguide/index.html
http://www.infoq.com/jp/articles/java-threading-optimizations-p1
エスケープ解析 - ロック除去
バイアスド・ロック
ロックの疎粒度化

http://download.oracle.com/docs/cd/E13188_01/jrockit/docs142/userguide/apstkdmp.html
Monitoring Information in Stack Dumps
Detecting Deadlocks

 

http://www.itarchitect.jp/news/-/107369.html

http://blogs.oracle.com/jrockit/2010/03/class_loading_deadlocks.html
Class Loading Deadlocks

 

http://blogs.oracle.com/jrockit/2010/01/why_wont_jrockit_find_my_class.html
Why won't JRockit find my classes

http://otndnld.oracle.co.jp/document/products/jrockit/docs142/userguide/apstkdmp.html