20070329 java programing tips
TRANSCRIPT
Javaオブジェクト指向プログラミングTIPS
目標
プログラミング勘を取り戻す 更なる向上も目指す 品質にかかわる基礎的なトピックスを理解する(等値性・コールバック)
フレームワークやサーバーサイドの技術的基礎となっているトピックスを理解する(リフレクション・マルチスレッド)
おまけ(シリアライゼーション)
等値性の実装
等値性の実装
String str1 = new String(“hoge”);String str2 = “hoge”;
System.out.println(str1 == str2);System.out.println(str1.equals(str2));
出力:FalseTrue
str1 == str2 : str1.equals(str2) : の比較
str2オブジェクトstr1オブジェクトchar[] value
==とequalsメソッド
str1
str2
str1オブジェクト
str2オブジェクト
char[] value
int length
boolean equals(Object obj)
char[] value
int length
boolean equals(Object obj)
スタック ヒープ
java.lang.Stringの中身public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ private final char value[]; private final int count; public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n-- != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; }
equalsメソッドをどうオーバーライドするか要求: 普通預金口座と定期預金口座を提供している銀行がある。顧客に総資産残高を提供するために、いずれか一方の口座番号からもう一方の口座番号を検索したい。
仕様: 口座番号上4桁を顧客コードとし、顧客コードが一致していれば口座種別にかかわらずTrueを返す。また、顧客コードが一致していなければ口座種別にかかわらずFalseを返す。方針: 抽象基底クラスAccountでequalsメソッドを実装する。普通預金口座と定期預金口座はAccountクラスを継承し、同じequalsメソッドによって上記仕様を満たすようにする。
口座の上位クラスの実装
public abstract class Account{ private char[] custmorCode = new char[4]; Account(char[] custmorCode){ this.custmorCode = custmorCode; } public char[] getCustmorCode(){ return this.custmorCode; } @Override public boolean equals(Object obj){ //ここをどう実装するか? }}
テストコード
SavingAccount a1 = new SavingAccount(new char[]{‘0’,’1’,’2’,’3’});SavingAccount a2 = new SavingAccount(new char[]{‘9’,’1’,’2’,’3’});
TimeDepositAccount a3 = new TimeDepositAccount(new char[]{‘0’,’1’,’2’,’3’});TimeDepositAccount a4 = new TimeDepositAccount(new char[]{‘9’,’1’,’2’,’3’});
System.out.println(a1.equals(a3));System.out.println(a2.equals(a4));System.out.println(a1.equals(a2));System.out.println(a2.equals(a3));
期待する出力:TrueTrueFalseFalse
getClassメソッドとinstanceof演算子
boolean equals(Object obj)
引数がObjectクラスなのでダウンキャストしなければならない
ClassCastExceptionを発生させないために検証が必要
instanceof演算子・オブジェクトが同じクラスから生成されている→ true・右辺が左辺の派生クラスから生成されている→ true・その他 → false
Class getClass()メソッド・オブジェクトが同じクラスから生成されている→ true・その他→ false
実装public abstract class Account{ //省略 @Override public boolean equals(Object obj){ if(obj instanceof Account){ //if(getClass() == obj.getClass()){ char[] compareCode = ((Account)obj).getCustmorCode(); if(custmorCode.length != compareCode.length){ return false; } for(int i = 0; i<custmorCode.length; i++){ if(custmorCode[i] != compareCode[i]){ return false; } } return true; } else { return false; } }}
実行
まとめ
equalsメソッドはクラスによって実装が異なる。java.lang.Objectは参照を比較するがStringは隠蔽しているchar[] valueを比較した結果を返す。
equalsメソッドを実装する際に、Instanceof演算子を使うかgetClassメソッドを使うかでequalsメソッドの挙動は変わってくるので注意する。
何を持って等しいとするかの定義があいまいだとバグが出やすい。自分がわかっていても使う人がわからないと意味がないので注意する。
コールバック
CASE:口座残高通知システム
要求: 顧客と顧客の債権者に、口座残高が一定額以下になった場合通知を出したい。
ポーリングループによる実装
仕様: 口座を監視するInspectorクラスを作り、口座残高が一定額以下になっているか検証する。
方針: Inspectorクラスを独立したスレッドにし、100msごとに口座残高をチェックする
ポーリングループによる実装
仕様: 口座を監視するInspectorクラスを作り、口座残高が一定額以下になっているか検証する。
方針: Inspectorクラスを独立したスレッドにし、100msごとに口座残高をチェックする
作業効率が著しく悪い
ポーリングループ(マルチスレッド)のイメージ
100msスレッドを止める 100msスレッドを止める 100msスレッドを止める 100msスレッドを止める 100msスレッドを止める・・・ スレッドはCPUを浪費し続ける
AccountInspector
コールバックによる実装
仕様: 口座を監視するInspectorクラスを作り、このクラスが口座から引き出しが行われたときに一定額以下になっているか検証する。
方針: 口座クラスの引き出しメソッドの実装を変更する。Inspectorクラスは口座クラスに登録し、引き出しメソッドが呼ばれると同時にInspectorに通知する。
コールバックによる実装
事前に通知対象をセットする(addInspectorメソッド) 引き出しがあったときのみInspectorに通知
public class Account{ private byte[] lock = new byte[0]; private ArrayList<Inspector> targets = new ArrayList<Inspector>(); public int get(int amount){ synchronized(lock){ if(this.amount < amount){ return 0; } for(Inspector i : targets){ i.inspect(); } this.amount = this.amount - amount; return amount; } } public void addInspector(Inspector insp){ this.targets.add(insp); }
まとめ
何らかの処理を待つ(オブジェクトが適当な状態になるまで待機する)処理は、ポーリングループとコールバックによって実装できる。
ポーリングループによる実装はCPUを浪費する。多分使わないほうがいい。
コールバックの実装は、通知すべきオブジェクトを設定し、状態が変化したときに通知を行う形式。CPUは浪費しない。設計上の見通しもよい(と思うよ。)
リフレクション
Stringによるインスタンスの生成public class Main{ public static void main(String[] args){ String className = "RefrectionTest"; RefrectionTest rt = null; try{ rt = (RefrectionTest)Class .forName(className).newInstance(); } catch(Exception e) { } rt.dosth(); }}
public class RefrectionTest{ public void dosth(){ System.out.println("Do Something"); }}
Stringによるメソッドの実行
public class Main{ public static void main(String[] args){ String className = "RefrectionTest"; RefrectionTest rt = null; try{ rt = (RefrectionTest)Class.forName(className).newInstance(); } catch(Exception e) { } String methodName = "dosth"; Method method = null; try{ method = (Method)Class.forName(className) .getMethod(methodName,null); } catch(Exception e) { } method.invoke(Class.forName(className).newInstance(),null); }}
シリアライゼージョン
シリアライゼージョンimport java.io.*;public class Main{ public static void main(String[] args){ Account a1 = new Account("hoge",5000); a1.get(100); try{ ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("hoge.ser")); oos.writeObject(a1); oos.close(); } catch(Exception e) {}
System.out.println(a1); Account ser = null; try{ ObjectInputStream ois = new ObjectInputStream( new FileInputStream("hoge.ser")); ser = (Account)ois.readObject(); ois.close(); } catch(Exception e) {}
ser.get(100); System.out.println(ser); }}
public class Account implements Serializable{ //省略 }
シリアライズされたファイルの中身
マルチスレッド
スレッド2口座残高を照会して100円追加
スレッド1口座残高を照会して100円追加
マルチスレッドの問題点
プライベートワーキングメモリ(スレッドに割り当てられるメモリ)と主メモリ内容の不一致
スレッド1が残高を変更しても、スレッド2のプライベートワーキングメモリは変更されないため口座の追加処理が正常に行われない
メインメモリ
残高0
残高0
100円に変更
100円に変更
プライベートワーキングメモリ プライベートワーキングメモリ
対策① synchronized
Synchonized:対象オブジェクトをモニタリングする。モニタリングしているオブジェクトがほかのスレッドで占有されている場合は処理待ちになる。
public class Account{ private int amount; private byte[] lock = new byte[0]; Account(int amount){ this.amount = amount; } public int get(int amount){ synchronized(lock){ this.amount = this.amount - amount; return amount; } } public void put(int amount){ synchronized(lock){ this.amount = this.amount + amount; } }}
スレッド2口座残高を照会して100円追加
スレッド1口座残高を照会して100円追加
対策① synchronized
スレッド2がスレッド1の残高更新前にメインメモリを照会してもブロックされているためアクセスできない
メインメモリ
残高0
100円に変更
残高0
200円に変更
プライベートワーキングメモリ プライベートワーキングメモリ
対策② volatile
プライベートワーキングメモリと主メモリの内容が常に一致するように保ちつづける
読み込みについてのみ有効、書き込みはsyncronizedさせないと不一致発生 基本的にsysnronizedより高速だが変数へのアクセスが頻繁に行われる場合メモリ間の一致処理にオーバーヘッドのため一概には言えなくなる
public class Account{ private volatile int amount; Account(int amount){ this.amount = amount; } public int getAmount(){ return this.amount; }}
スレッド2口座残高を照会する
スレッド1口座残高を照会して100円追加
対策② volatile
スレッド1の残高更新後にメインメモリとプライベートワーキングメモリの内容を一致させるためエラーが起きない
厳密にはスレッド2での変数使用時にメインメモリを必ず読み込むためバグにならない
メインメモリ
残高0
残高0
100円に変更
100円で読込
プライベートワーキングメモリ プライベートワーキングメモリ
対策③ Java.util.concurrentimport java.util.concurrent.atomic.*;
public class Account{ private AtomicInteger amount; Account(int amount){ this.amount = new AtomicInteger(amount); } public int get(int amount){ if(this.amount.get() < amount){ return 0; } do{ if(this.amount.get() < amount){ break; } } while(!this.amount.compareAndSet(this.amount.get(), this.amount.get() - amount)); return amount; } public void put(int amount){ do{ if(amount < this.amount.get()){ break; } } while(!this.amount.compareAndSet(this.amount.get(), this.amount.get() + amount)); } public int getAmount(){ return amount.get(); }}
AtomicInteger
CAS実装を一部抜粋 CASによる変数アクセスの実装 Int型をクラスでラッピングしてデータへのアクセスをCASで実装
public class AtomicInteger extends Number implements java.io.Serializable { private volatile int value;public AtomicInteger(int initialValue) { value = initialValue;}public final int get() { return value;}public final void set(int newValue) { value = newValue;}public final int getAndSet(int newValue) { for (;;) { int current = get(); if (compareAndSet(current, newValue)) return current; }}public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}
}
CAS Compare And Swap
プロセッサ上のアトミックな命令
読込時点と書込時点の値が一致していれば書込みを実行する
スレッド2の残高書込時点で、プライベートワーキングメモリにある残高0円とCASで取得した値(メインメモリの100円)が一致していないため、再度値の書込みを試みる
スレッド2口座残高を照会する
スレッド1口座残高を照会して100円追加
メインメモリ
残高0
残高0
100円に変更
CAS失敗再度読み込み値の更新
プライベートワーキングメモリ プライベートワーキングメモリ
まとめ
synchonizeは対象オブジェクトをロックする。同じロックを複数スレッドが持つことができないようになっている。
volatileは読込の際にメインメモリとプライベートワーキングメモリを一致させる。
JDK5.0で追加されたjava.util.concurrentでマルチスレッドがサポートされている。Javaを使うときにプロセッサレベルで高速なプログラムを書くにはこのパッケージを使う。
おわりに
特に参考にした本です