- PR -

HashMapの同期化について

投稿者投稿内容
m-taka
会議室デビュー日: 2003/05/27
投稿数: 8
投稿日時: 2003-06-05 19:18
HashMapにおいて、rehashが行われないという前提(十分な容量を確保しておく)
であれば、1つのputスレッドと、他のgetスレッド間で同期化しなくとも
問題ないでしょうか?
もちろん、put中の値を、getするスレッドは無いものとし、getは
既にput完了済みの値に対するものだけとした場合です。

よろしくお願いします。
呂布
会議室デビュー日: 2003/03/07
投稿数: 16
お住まい・勤務地: Tokyo
投稿日時: 2003-06-05 20:32
引用:

もちろん、put中の値を、getするスレッドは無いものとし、
getは既にput完了済みの値に対するものだけとした場合です。



put中にHashMapオブジェクトを同期化してくれるなら、takaさんの質問にはOKと答えることができます。

が、設計として問題アリと言わざるをえません。やめましょう。
そのオブジェクトに対して、put()だけではなく、構造を変更する操作の全ての場面で、全てのプログラマがそのように考慮しなくてはならなくなります。そんなことは、事実上不可能です。今日はできても、明日誰かが作ったソースコードはまでは保証できません。

もしあなたがそのHashMapを複数のスレッドで共有するつもりなら、Hashtableを使用するか Collections.synchronizedMap()によって生成されたMapを使用すべきです。

容量を多く見積もってリハッシュされない(だろう)Mapを生成することで無駄にメモリを使うよりも、最小限に見積もられ、負荷係数を吟味した、同期化されたMapを生成すべきです。

危険な道をうまく渡れると思わず、安全な道を歩くようにしましょう。Hashtableはtakaさんが考えるほど、致命的なオーバーヘッドにはなりません。むしろスピードを考慮するなら、他にリファクタリングされるべき箇所があるはずです。
m-taka
会議室デビュー日: 2003/05/27
投稿数: 8
投稿日時: 2003-06-06 09:23
呂布さん、返信ありがとうございます。

呂布さんのおっしゃる通り、get操作がsynchronizedされることによる
オーバヘッドを考慮しての書き込みでした。
今、実装したいものは、コレクション要素は一度登録されたら変更される
ことはなく、システム開始後、しばらくすればputもほとんど発生しなくなる性質の
ものです。で、getがシンクロされるのもいまいちだなぁと。
スレッド数は、数百です。コレクション要素も数百。

実際、HashTableでのgetは、マイクロ秒以下のオーダですし、
システム全体から見た場合には、事実上影響ありません。

やっぱHashTable(シンクロ)を使うんでしょうね。

どうもありがとうございました。m(_ _)m

TAKA
Wata
ぬし
会議室デビュー日: 2003/05/17
投稿数: 279
投稿日時: 2003-06-06 10:18
こういうケースに
commons-collectionsのFastHashMapを使うといいのかな?
ちょうど記事が載ってるし。
m-taka
会議室デビュー日: 2003/05/27
投稿数: 8
投稿日時: 2003-06-06 11:11
引用:

Wataさんの書き込み (2003-06-06 10:18) より:
こういうケースに
commons-collectionsのFastHashMapを使うといいのかな?
ちょうど記事が載ってるし。


Wataさん、こんにちは。ありがとうございます。
ほんとに良いタイミングで掲載されていますね。

で、ソースをダウンロードし、FastHashMapを見てみました。
記事にある通り、モード設定を切り替えるという特徴があります。
以下はソースからの抜粋です。

public void setFast(boolean fast) {
this.fast = fast;
}
public Object put(Object key, Object value) {
if (fast) {
synchronized (this) {
HashMap temp = (HashMap) map.clone();
Object result = temp.put(key, value);
map = temp;
return (result);
}
} else {              <----------@
synchronized (map) {
return (map.put(key, value));
}
}
}
public Object get(Object key) {
if (fast) {             <----------A
return (map.get(key));
} else {
synchronized (map) {
return (map.get(key));
}
}
}

良く分からないのは、fast=falseの状態で、あるputスレッドが@に来たとします。
そこで他のスレッドがsetFast(true)とします。
さらに他のスレッドが、Aに来くると、fastモードのgetに入ります。
この場合、mapはputとgetの間で同期されないですよね。

使う前提として、put及びsetFast(true)する人を限定するってことですか?
記事のような例では確かに問題ないと思いますが、インスタンス生成後にも
少ないながらもputスレッドが発生するケースではどうなるのでしょうか。
ドキュメントには何も書いてないようで、それとも私の上の解釈が
誤ってますでしょうか?(誤っているといいなぁ)

どなたかフォローをお願いします。

追伸
takaというハンドルでスレッドを起こしましたが、他にもtakaさんが
いらっしゃるので、m-takaとしました。

m-taka
会議室デビュー日: 2003/05/27
投稿数: 8
投稿日時: 2003-06-06 11:19
失礼しました。コード部分の再送です。

コード:
    public void setFast(boolean fast) {
        this.fast = fast;
    }
    public Object put(Object key, Object value) {
        if (fast) {
            synchronized (this) {
                HashMap temp = (HashMap) map.clone();
                Object result = temp.put(key, value);
                map = temp;
                return (result);
            }
        } else {              <----------@
            synchronized (map) {
                return (map.put(key, value));
            }
        }
    }
    public Object get(Object key) {
        if (fast) {             <----------A
            return (map.get(key));
        } else {
            synchronized (map) {
                return (map.get(key));
            }
        }
    }


Wata
ぬし
会議室デビュー日: 2003/05/17
投稿数: 279
投稿日時: 2003-06-06 19:02
Wataです。自分もよく分からないのですが、
引用:

使う前提として、put及びsetFast(true)する人を限定するってことですか?


については、setFast(true)をする人を限定するのだと思います。
putはsetFast(true)の状態でも実行可能ですが、
クローンを作るので通常より(かなり?)遅くなります。

しかし、ソースの同期の取り方がどうも理解できない。
多分、自分がマルチスレッドをよく理解できてないだけだと思うのですが、
次のようにしたほうがいいような気がするのですが...。

コード:

public void setFast(boolean fast) {
this.fast = fast;
}
public Object put(Object key, Object value) {
if (fast) {
HashMap temp;
synchronized (map) { <----------mapに変更
temp = (HashMap) map.clone();
}       <----------クローンを作ったらロック解放

Object result = temp.put(key, value);
map = temp;
return (result);
} else {          
synchronized (this) {  <----------thisに変更
return (map.put(key, value));
}
}
}
public Object get(Object key) {
if (fast) {         
return (map.get(key));
} else {
synchronized (this) { <----------thisに変更
return (map.get(key));
}
}
}


私にも、どなたかのフォローをお願いします。
# 追加:以上、単純な勘違いでした...。ですのでフォローはいらないです。

[ メッセージ編集済み 編集者: Wata 編集日時 2003-06-09 09:23 ]

[ メッセージ編集済み 編集者: Wata 編集日時 2003-06-09 09:24 ]
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-06-06 19:54
unibon です。こんにちわ。

引用:

m-takaさんの書き込み (2003-06-06 09:23) より:
呂布さんのおっしゃる通り、get操作がsynchronizedされることによる
オーバヘッドを考慮しての書き込みでした。
今、実装したいものは、コレクション要素は一度登録されたら変更される
ことはなく、システム開始後、しばらくすればputもほとんど発生しなくなる性質の
ものです。で、getがシンクロされるのもいまいちだなぁと。


#以下、ちょっと外しているかもしれませんが。
Hashtable の実装だと、get と put で共に Hashtable 自身のインスタンスを
synchronized していると思いますが、これは簡易的な同期の仕方だと思います。
DBMS の共有ロックと排他ロックでたとえると、
排他ロックと共有ロックで分けてあるので、
共有ロックが複数あっても軽いロックですむのが普通であり、
あまり共有ロックのコストを意識することはないですよね。
しかし、これと同じことを、オンメモリの Java でエントリ数の少ない Hashtable でやろうとして、
下手に凝った同期の仕方を実装するとそれが逆にコスト増につながるので、
Hashtable はそうしていないだけかもしれません(か、単なる手抜き)。

もしも get が多く put が少ないという利用方法ならば、
それ用の同期を備えた Map を自前で実装すれば良く、
単にそれが標準で備わっていないだけ、と考えたほうが良いような気がします。

とここまで書いてから、
引用:

Wataさんの書き込み (2003-06-06 10:18) より:
こういうケースに
commons-collectionsのFastHashMapを使うといいのかな?
ちょうど記事が載ってるし。


を拝見したのですが
私はこのクラスの存在をはじめて知りました。
http://jakarta.apache.org/commons/collections/api/org/apache/commons/collections/FastHashMap.html
を見ると、clone を使っているそうなのですが、その理由が良く分かりません。
単に、同期用のフラグを get 用と put 用に分けるような感じにするだけで
できそうだと思っているのですが、でも、これは私の大きな勘違いかもしれません。

スキルアップ/キャリアアップ(JOB@IT)