Java

お正月なので ぷるんぷるん やってみた

Java

新年あけましておめでとうございます。

本年もどうぞよろしくお願いします。

さて、2016年になったからって私が私であることにはかわりない。

いつものようにふざけて楽しんでいけたらそれだけで幸せです。

ということで、今年もおっぱいネタでスタートです。

いつからかは覚えてないけど12月は Advent Calendar ってのがあって忙しい中1日から25日までリレー方式である特定のカテゴリーのブログをかくイベントがあります。

その中で JavaFX Advent Calendar 2015 ってのがあって私にとっては知らない技術を知るいい機会となっています。

今日のおっぱいネタはその中の一つの技術を使えば男の浪漫(女性からみたら単なる馬鹿)を満たせると思いチャレンジした記録です。

おもいっきりふざけた内容ですのでご容赦くださいませ。

では、どんな技術を使おうとしているかは元ネタのエントリーを読んでください。

とても丁寧に解説されています。

JavaFX in the Box Displacement Map

この DisplacementMap と FloatMap クラスが鍵となるようです。

これって聞いたこともみたこともないなぁ・・

DisplacementMap はエフェクトの紹介でちらっと名前だけは見たことあるけどこれを使ってこんなことができるよってのは記憶にない。

JavaDoc にはテキストを変形させるサンプルコードの断片がありますね。

そんなことやっても面白くないよ。

そこでネタ元でもレンズ効果を試していたけど時間切れで手抜きによる不本意な結果となってます。

時間があれば再チャレンジすると書いてあったのでゆっくり待ちましょう。(^_^)

それでも男の浪漫達成にはレンズ効果は必須なので数学力の乏しい私は適当にあーでもない、こうでもない、と本当にいい加減に進めます。

理想とするカーブを得る関数を作れる人生を送りたかった。(>_<。)

とりあえず、レンズ効果を得るためにプログラムを組んでみた。

と言ってもネタ元のコードをほとんど美味しくいただいてます。

これを実行するとこうなります。

エフェクトをかける前の画像です。

1

 

エフェクトをかけた画像です。

2

 

中央が盛り上がって少しはレンズ効果が出ているようです。

ただ、境界がもっとスムースになるようにしたいですね。

このエフェクトを使えば貧乳を救えるかもしれない。 |:゚●=(゚ェ゚`)o グチャ

ちょっと FloatMap の計算過程で Y 軸の値を変更してみました。

解りにくいかも知れませんが歪みながら上下に移動しています。

3

 

4

 

これを上手く利用できれば浪漫に一歩近づくことができるかもしれない。

とりあえず、先ほどのプログラムをクリックした位置を中心にレンズ効果を得られるように変更してみました。

すみません。もう変更ずみでした。

元画像です。

5

レンズエフェクトを適用した結果画像です。

ううっ

やはりちゃんとしたカーブを得る関数でプログラムを動かしてないので厳しいですね。

6

ちなみにタレントの堀北真希さんの画像を使ってレンズ効果を試したらあちこちから怒られてしまったので今回はファンのいない一般人の少女に頑張ってもらいました。

だけどくじけてはいけない!

先ほどのレンズ効果でも目的は達成できるかもしれない。いや、できると信じよう!

おっぱい星人である私はがんばりました

そこそこ楽しめるプログラムができました。

このプログラムのソースコードを載せようと思ったけど長くなる割に前述のプログラムをアニメーションタイマーで動かしただけのようなものなので止めときます。

レンズ効果の部分の数値はちょっと変更してあります。

重要なことを忘れてました。年末の忙しい時にこんな楽しいふざけたプログラムを組んでると家族から冷ややかな視線をあびることになります。

男の浪漫は常に危険と隣り合わせってことです!

年越しプログラミングで作った新年早々のプログラム、まだまだだけどネタ的には満足だ!(^_^)

Java 最高!

Hatena タグ:

よく見かけるネタ IntegerCache 問題

Java

昔からよくある Java パズルでブログの更新を久しぶりにしておきます。

次のコードの結果はどうなるでしょう?

true
false
true
false

となります。

間違えてませんよね!

今さらですが、これらは Integer.valueOf() メソッドが使われて -128 から 127 までは IntegerCache クラスであらかじめ用意されているものが使われる。

小さな親切、大きなお世話というやつです!

でも、用意してある物は使わなくては損ですよね。

じゃあ、どうしてもっといっぱい作って用意しておいてくれないの?って言うあなたに

JVM の実行時引数による制御も可能です。

-Djava.lang.Integer.IntegerCache.high=<size>

それとも、

-XX:AutoBoxCacheMax=<size>

で OK です。

-Djava.lang.Integer.IntegerCache.high=128 と設定して実行すると先ほどのプログラムの結果は

true
true
true
false

となります。

ちなみに IntegerCache の最小値の設定方法は私は知りません。(;゜ロ゜)

以上、よくあるネタ終了です。

Hatena タグ:

 

 

 

 

 

 


Java 8 時代の ConcurrentHashMap で遊んでみた。

Java

またまた Blog の更新をさぼっています。

と言うことで適当なエントリーを書いてみます。

ConcurrentHashMap

J2SE5.0 で java.util.concurrent に ConcurrentHashMap が導入されました。

ConcurrentHashMap は容易に安全に並行処理を実現するために作られました。

Collections.synchronizedMap ってのもあるのですが、それは使い方が微妙で詳しい説明は省略しますが Iterator などの反復処理で ConcurrentModificationException をスローすることがあります。

Collections.synchronizedMap は、Map のロックを取得すると他のスレッドからアクセスを禁じられます。

ConcurrentHashMap だと、Iterator および Enumeration は、ある時点または反復子/列挙の作成以降のハッシュテーブルの状態を反映する要素を返すように設計されているため、

ConcurrentModificationException をスローすることはありません。

しかし、一度に 1 つのスレッドだけしか反復子を使用できません。

あと、HashMap はエントリーのキーや値にnullを使用できますが、ConcurrentHashMapでは使用できません。

NullPointerException が投げられます。

ConcurrentHashMap は、HashMap のようなハッシュを使った Map ですがロックストライピングと言われる粒度の小さなロック方式を採用しています。

つまり、Map 全体をロックして排他アクセスする必要がなければ  ConcurrentHashMap は、とても魅力的な存在となります。

しかし、J2SE5.0 の時には、ConcurrentHashMap を便利に使うにはまだまだパーツが揃っていませんでした。

便利に使えそうだったのはアトミックに実行される下記メソッドくらいでした。

public V putIfAbsent(K key, V value)

public boolean remove(Object key, Object value)

public V replace(K key, V value)

public boolean replace(K key, V oldValue, V newValue)

また古い J2SE5.0 ネタかと思わせて、ここから最新の Java SE 8 ネタです。

Java SE 8 で ConcurrentHashMap に追加された便利な機能をいくつか試してみます。

はじめに、ConcurrentHashMap というネーミングから何をやってもスレッドセーフだと思い込んでしまうと泣きます。

例えば、下記プログラムのように複数スレッドから ConcurrentHashMap の値の更新があるとします。

最終的に Key “HOGE”, Value は 80 となることを期待しますが残念なことになります。

このプログラムの実行結果は次のようになります。

ForkJoinPool.commonPool-worker-18: HOGE, 48
ForkJoinPool.commonPool-worker-29: HOGE, 58
ForkJoinPool.commonPool-worker-8: HOGE, 65
ForkJoinPool.commonPool-worker-4: HOGE, 67
ForkJoinPool.commonPool-worker-11: HOGE, 68
ForkJoinPool.commonPool-worker-15: HOGE, 70
ForkJoinPool.commonPool-worker-25: HOGE, 73
ForkJoinPool.commonPool-worker-22: HOGE, 75
main : HOGE, 75

更新処理まではスレッドセーフでないということですね。

それではどのように対処すればいいでしょうか?

java.util.concurrent.atomic.LongAdder を使ってみます。

LongAdder クラスは、プリミティブ型に対するアトミック操作を行うため Java SE 8 から追加されたクラスです。

LongAdder クラスには、add(1)と等価な increment() メソッドと、add(-1)と等価な decrement() メソッドがあります。

LongAdder クラスを使って修正したプログラムは下記のようになります。

このプログラムの実行結果は次のようになります。

ForkJoinPool.commonPool-worker-25: HOGE, 49
ForkJoinPool.commonPool-worker-18: HOGE, 63
ForkJoinPool.commonPool-worker-11: HOGE, 71
ForkJoinPool.commonPool-worker-4: HOGE, 72
ForkJoinPool.commonPool-worker-29: HOGE, 73
ForkJoinPool.commonPool-worker-8: HOGE, 78
ForkJoinPool.commonPool-worker-15: HOGE, 79
ForkJoinPool.commonPool-worker-22: HOGE, 80
main : HOGE, 80

期待通りにうごいてくれました。

でも、ConcurrentHashMap<String, LongAdder> とかに変更するのも面倒ですよね。

それでは、Java SE 8 でのもっと素敵な修正プログラムを紹介します。

下記のようなアトミック操作をするメソッドを利用します。

public V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)

public V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)

public V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)

compute メソッドは、指定されたキーと現在マップされている値に対するマッピングの計算を試みます(現在のマッピングが存在しない場合はnull)。

computeIfPresent メソッドは、指定されたキーの値が存在する場合、キーと現在マップされている値から新しいマッピングの計算を試みます。

merge メソッドは、指定されたキーがまだ(nullでない)値と関連付けられていない場合は、指定された値に関連付けます。それ以外の場合は、指定された再マッピング関数の結果で値を置換し、nullの場合は削除します。

今回は、computeIfPresent メソッドを使ってみました。

他のメソッドの使用例はコメントアウトして記述してあります。

プログラムは下記のようになります。

このプログラムの実行結果は次のようになります。

ForkJoinPool.commonPool-worker-4: HOGE, 59
ForkJoinPool.commonPool-worker-15: HOGE, 61
ForkJoinPool.commonPool-worker-18: HOGE, 62
ForkJoinPool.commonPool-worker-22: HOGE, 68
ForkJoinPool.commonPool-worker-11: HOGE, 74
ForkJoinPool.commonPool-worker-25: HOGE, 78
ForkJoinPool.commonPool-worker-8: HOGE, 79
ForkJoinPool.commonPool-worker-29: HOGE, 80
main : HOGE, 80

これまでのところは J2SE5.0 でも注意が必要で replace メソッドを使う方法、AtomicLong を使うなどの方法がありました。

Java SE 8 では綺麗にその問題を解決できるようになりましたね!

それでは、これから Java SE 8 で追加されたスレッドセーフなバルクオペレーションを試してみます。

下記のプログラムを組んで、Java SE 8 で ConcurrentHashMap に追加された機能の一部を試してみました。

このプログラムの実行結果は次のようになります。

CPU: 32
ForkJoinPoolParallelism: 7

マッピング数: 18

(○・ω・)ノ————- forEach (BiConsumer) ————-
main: JDK 1.1.7 = Brutus
main: Java SE 9 =
main: JDK 1.1.8 = Chelsea
main: Java SE 8 = null
main: JDK 1.1.5 = Pumpkin
main: JDK 1.1.6 = Abigail
main: J2SE 1.2 = Playground
main: JDK 1.1.4 = Sparkler
ForkJoinPool.commonPool-worker-3: J2SE 1.2.2 = Cricket
main: Java SE 7 = Dolphin
ForkJoinPool.commonPool-worker-3: J2SE 1.3.1 = Ladybird
ForkJoinPool.commonPool-worker-4: J2SE 1.4 = Merlin
ForkJoinPool.commonPool-worker-1: J2SE 1.4.2 = Mantis
ForkJoinPool.commonPool-worker-2: J2SE 1.3 = Kestrel
main: Java SE 6 = Mustang
ForkJoinPool.commonPool-worker-5: J2SE 1.4.1 = Hopper
ForkJoinPool.commonPool-worker-4: J2SE 5.0 = Tiger
ForkJoinPool.commonPool-worker-3: J2SE 1.2.1 = null

(○・ω・)ノ————- forEach (BiFunction, Consumer) ————-
ForkJoinPool.commonPool-worker-5: J2SE 1.3.1 = Ladybird
main: J2SE 1.2 = Playground
ForkJoinPool.commonPool-worker-1: JDK 1.1.4 = Sparkler

(○・ω・)ノ————- forEachEntry (Function Consumer)————-
ForkJoinPool.commonPool-worker-1: J2SE 1.3.1 = Ladybird
ForkJoinPool.commonPool-worker-6: JDK 1.1.4 = Sparkler
ForkJoinPool.commonPool-worker-7: J2SE 1.2 = Playground

(○・ω・)ノ————- search ————-
searchResult: J2SE 1.3.1 = Ladybird

(○・ω・)ノ————- searchKeys ————-
searchKeysResult: J2SE 1.2.2

(○・ω・)ノ————- searchValues ————-
searchValuesResult: Sparkler

(○・ω・)ノ————- searchEntries ————-
searchEntriesResult: JDK 1.1.4 = Sparkler

(○・ω・)ノ————- reduce ————-
reduceResult
JDK 1.1.7 = Brutus
Java SE 9 =
JDK 1.1.8 = Chelsea
Java SE 8 = null
JDK 1.1.5 = Pumpkin
JDK 1.1.6 = Abigail
J2SE 1.2 = Playground
JDK 1.1.4 = Sparkler
Java SE 7 = Dolphin
Java SE 6 = Mustang
J2SE 1.2.2 = Cricket
J2SE 1.3.1 = Ladybird
J2SE 1.2.1 = null
J2SE 1.3 = Kestrel
J2SE 1.4 = Merlin
J2SE 5.0 = Tiger
J2SE 1.4.2 = Mantis
J2SE 1.4.1 = Hopper

(○・ω・)ノ————- reduceKeys ————-
reduceKeysResult
J2SE 1.2.2
J2SE 1.3.1
J2SE 1.2.1
J2SE 1.4.2
J2SE 1.4.1

(○・ω・)ノ————- reduceValues ————-
reduceValuesResult
Brutus
Merlin
Tiger
Mantis
Hopper

(○・ω・)ノ————- reduceEntries ————-
reduceEntriesResult
J2SE 1.2 = Playground
JDK 1.1.4 = Sparkler
J2SE 1.3.1 = Ladybird

(○・ω・)ノ————- reduceMaxValuesResult ————-
10

(○・ω・)ノ————- お終い! ————-

 

それでは、プログラムをざっくり追ってみましょう。

16 行目でこのプログラムを実行しているコンピュータの論理 CPU 数を取得しています。

そして、VM オプションを -Djava.util.concurrent.ForkJoinPool.common.parallelism=7 としてあるので正しく設定されているか確認を 18 行目のコードで行っています。

// CPU
System.out.println(“CPU: ” + ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors());
// -Djava.util.concurrent.ForkJoinPool.common.parallelism=7
System.out.println(“ForkJoinPoolParallelism: ” + ForkJoinPool.getCommonPoolParallelism() + System.getProperty(“line.separator”));

1

20 行目から 38 行目にかけて、ConcurrentHashMap を生成してデータを挿入しています。

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put(“JDK 1.1.4”, “Sparkler”);
.
.
map.put(“Java SE 9”, “”);

40 行目でマッピング数を取得しています。

System.out.println(“マッピング数: ” + map.mappingCount()
        + System.getProperty(“line.separator”));

public long mappingCount() は、API ドキュメントによると次のように書かれています。

マッピングの数を返します。ConcurrentHashMapにはintで表すことができる数より多くのマッピングが含まれている可能性があるため、

size()のかわりにこのメソッドを使用するようにしてください。返される値は推定値であり、挿入や削除が同時に行われた場合、実際のカウントは異なる可能性があります。

大きな要素を持つ場合は size() の代わりに mappingCount() を使うことができるようになったようですね。

forEach

43 行目から 57 行目までは forEach を試しています。

forEach は次のように 9 種類用意されています。

引数に Function, BiFunction のような関数を含むものもあり便利に使えます。

public void forEach(BiConsumer<? super K,? super V> action)

public void forEach(long parallelismThreshold,
                    BiConsumer<? super K,? super V> action)

public <U> void forEach(long parallelismThreshold,
                        BiFunction<? super K,? super V,? extends U> transformer,
                        Consumer<? super U> action)

public void forEachKey(long parallelismThreshold,
                       Consumer<? super K> action)

public <U> void forEachKey(long parallelismThreshold,
                           Function<? super K,? extends U> transformer,
                           Consumer<? super U> action)

public void forEachValue(long parallelismThreshold,
                         Consumer<? super V> action)

public <U> void forEachValue(long parallelismThreshold,
                             Function<? super V,? extends U> transformer,
                             Consumer<? super U> action)

public void forEachEntry(long parallelismThreshold,
                         Consumer<? super Map.Entry<K,V>> action)

public <U> void forEachEntry(long parallelismThreshold,
                             Function<Map.Entry<K,V>,? extends U> transformer,
                             Consumer<? super U> action)

今回のプログラムでは、

public void forEach(long parallelismThreshold,
                    BiConsumer<? super K,? super V> action)

public <U> void forEach(long parallelismThreshold,
                        BiFunction<? super K,? super V,? extends U> transformer,
                        Consumer<? super U> action)

public <U> void forEachEntry(long parallelismThreshold,
                             Function<Map.Entry<K,V>,? extends U> transformer,
                             Consumer<? super U> action)

を使っています。

第一引数の parallelismThreshold は、このオペレーションを並列的に実行するために必要な(推定の)要素数となっています。いわゆる閾値です。

並行処理をするために第一引数の parallelismThreshold を 1 と設定しています。

それで本当に並行処理が行われているか確認のためにスレッド名を表示させています。

ちょっと注意がいるのは、public void forEach(BiConsumer<? super K,? super V> action) ですね。

これ、引数に parallelismThreshold がありません。

つまり、並行処理されません。

残りの引数は Java SE 8 ではお馴染みの関数ですから特に説明することはないでしょう。

一つだけ API ドキュメントの内容を紹介しておきます。

public <U> void forEach(long parallelismThreshold,
                        BiFunction<? super K,? super V,? extends U> transformer,
                        Consumer<? super U> action)

各(キー, 値)のnullでない各変換に対し、指定されたアクションを実行します。

型パラメータ:
U – トランスフォーマの戻り値の型

パラメータ:
parallelismThreshold – このオペレーションを並列的に実行するために必要な(推定の)要素数
transformer – 要素の変換を返す関数。変換がない場合はnull(その場合、アクションは適用されない)
action – アクション

個人的にはこれが一番使い勝手が良いかなって思っています。

それでは三つの forEach の実行結果を確認します。

はじめに第二引数に BiConsumer を持つ forEach の処理です。

map.forEach(1,
        (key, value) -> System.out.println(Thread.currentThread().getName() + “: ” + key + ” = ” + value));

戻り値無しの処理でスレッド名と key, value を一つの文字列とします。(解りやすいように連結用の文字を追加しています。)

(○・ω・)ノ————- forEach (BiConsumer) ————-
main: JDK 1.1.7 = Brutus
main: Java SE 9 =
main: JDK 1.1.8 = Chelsea
main: Java SE 8 = null
main: JDK 1.1.5 = Pumpkin
main: JDK 1.1.6 = Abigail
main: J2SE 1.2 = Playground
main: JDK 1.1.4 = Sparkler
ForkJoinPool.commonPool-worker-3: J2SE 1.2.2 = Cricket
main: Java SE 7 = Dolphin
ForkJoinPool.commonPool-worker-3: J2SE 1.3.1 = Ladybird
ForkJoinPool.commonPool-worker-4: J2SE 1.4 = Merlin
ForkJoinPool.commonPool-worker-1: J2SE 1.4.2 = Mantis
ForkJoinPool.commonPool-worker-2: J2SE 1.3 = Kestrel
main: Java SE 6 = Mustang
ForkJoinPool.commonPool-worker-5: J2SE 1.4.1 = Hopper
ForkJoinPool.commonPool-worker-4: J2SE 5.0 = Tiger
ForkJoinPool.commonPool-worker-3: J2SE 1.2.1 = null

 

次は、引数が三つの forEach で第二引数に BiFunction を第三引数に Consumer を持っています。

map.forEach(1,
        (key, value) -> key.length() > 7 && value.length() > 7 ? Thread.currentThread().getName() + “: ” + key + ” = ” + value : null,
        System.out::println);

第二引数の BiFunction で key が 7 文字より長くかつ value が 7 文字より長い値をフィルタリングし、スレッド名と key, value を一つの文字列として返しています。(解りやすいように連結用の文字を追加しています。)

第三引数の Consumer ではそれらを標準出力に表示させています。

(○・ω・)ノ————- forEach (BiFunction, Consumer) ————-
ForkJoinPool.commonPool-worker-5: J2SE 1.3.1 = Ladybird
main: J2SE 1.2 = Playground
ForkJoinPool.commonPool-worker-1: JDK 1.1.4 = Sparkler

 

次の forEach は、forEachEntry で Map.Entry に操作を加えます。

map.forEachEntry(1,
        entry -> entry.getKey().length() > 7 && entry.getValue().length() > 7 ? Thread.currentThread().getName() + “: ” + entry.getKey() + ” = ” + entry.getValue() : null,
        System.out::println);

第二引数の Function で、Entry の key と value をそれぞれ取得、そして key が 7 文字より長くかつ value が 7 文字より長い値をフィルタリングし、スレッド名と key, value を一つの文字列として返しています。(解りやすいように連結用の文字を追加しています。)

第三引数の Consumer でではそれらを標準出力に表示させています。

(○・ω・)ノ————- forEachEntry (Function Consumer)————-
ForkJoinPool.commonPool-worker-1: J2SE 1.3.1 = Ladybird
ForkJoinPool.commonPool-worker-6: JDK 1.1.4 = Sparkler
ForkJoinPool.commonPool-worker-7: J2SE 1.2 = Playground

forEach だけでも至れり尽くせりで便利に使えますね。(^_^)

Function, BiFunction が使えるのって幸せです!

search

それでは search を見ていきましょう。

59 行目から 78 行目まで  search を試しています。

search は 4 種類の優れたメソッドが追加されました。

それぞれ API ドキュメントの内容を見ていきましょう。

public <U> U search(long parallelismThreshold,
                    BiFunction<? super K,? super V,? extends U> searchFunction)

指定された検索関数を各(キー、値)に適用し、nullでない結果を返します(存在しない場合はnull)。
成功した場合、その後の要素の処理は抑制され、検索関数の他の並列呼出しの結果は無視されます。

型パラメータ:
U – 検索関数の戻り値の型

パラメータ:
parallelismThreshold – このオペレーションを並列的に実行するために必要な(推定の)要素数
searchFunction – 成功した場合はnull以外の結果を返し、それ以外の場合はnullを返す関数。

戻り値:
各(キー、値)に対して指定された検索関数を適用した場合のnull以外の結果。存在しない場合はnull

 

public <U> U searchKeys(long parallelismThreshold,
                        Function<? super K,? extends U> searchFunction)

各キーに指定された検索関数を適用したnull以外の結果を返します。結果がない場合はnullを返します。
成功した場合、その後の要素の処理は抑制され、検索関数の他の並列呼出しの結果は無視されます。

型パラメータ:
U – 検索関数の戻り値の型

パラメータ:
parallelismThreshold – このオペレーションを並列的に実行するために必要な(推定の)要素数
searchFunction – 成功した場合はnull以外の結果を返し、それ以外の場合はnullを返す関数。

戻り値:
各キーに対して指定された検索関数を適用した場合のnull以外の結果。存在しない場合はnull

 

public <U> U searchValues(long parallelismThreshold,
                          Function<? super V,? extends U> searchFunction)

各値に指定された検索関数を適用したnull以外の結果を返します。結果がない場合はnullを返します。
成功した場合、その後の要素の処理は抑制され、検索関数の他の並列呼出しの結果は無視されます。

型パラメータ:
U – 検索関数の戻り値の型

パラメータ:
parallelismThreshold – このオペレーションを並列的に実行するために必要な(推定の)要素数
searchFunction – 成功した場合はnull以外の結果を返し、それ以外の場合はnullを返す関数。

戻り値:
各値に指定された検索関数を適用したnull以外の結果。存在しない場合はnull

 

public <U> U searchEntries(long parallelismThreshold,
                           Function<Map.Entry<K,V>,? extends U> searchFunction)

各エントリに指定された検索関数を適用したnull以外の結果を返します。結果がない場合はnullを返します。
成功した場合、その後の要素の処理は抑制され、検索関数の他の並列呼出しの結果は無視されます。

型パラメータ:
U – 検索関数の戻り値の型

パラメータ:
parallelismThreshold – このオペレーションを並列的に実行するために必要な(推定の)要素数
searchFunction – 成功した場合はnull以外の結果を返し、それ以外の場合はnullを返す関数。

戻り値:
各エントリに指定された検索関数を適用したnull以外の結果。存在しない場合はnull

API ドキュメントを見るとそれぞれ key と value に対する操作、key に対する操作、value に対する操作、Map.Entry に対する操作を行うように分けられているようです。

search でも Function, BiFunction が便利に検索関数として使われています。

それでは順番にプログラムの処理を見ていきましょう。

key と value による検索

        String searchResult = map.search(1, (key, value) -> key.length() > 7 && value.length() > 7 ? key + ” = ” + value : null);

key が 7 文字より長くかつ value が 7 文字より長い値をフィルタリングし、key, value を一つの文字列として返しています。(解りやすいように連結用の文字を追加しています。)

プログラムの実行結果は次のようになりました。

(○・ω・)ノ————- search ————-
searchResult: J2SE 1.3.1 = Ladybird

API ドキュメントによれば、複数検索条件に該当する場合、最初に検索に成功した一つだけが選択され、他は破棄されます。

よって検索条件に該当するものが複数ある場合必ず結果が同じになるとは限らない。

key による検索

String searchKeysResult = map.searchKeys(1, key -> key.length() > 7 ? key : null);

key が 7 文字より長い値をフィルタリングし、key を返しています。

プログラムの実行結果は次のようになりました。

(○・ω・)ノ————- searchKeys ————-
searchKeysResult: J2SE 1.2.2

value による検索

String searchValuesResult = map.searchValues(1, value -> value.length() > 7 ? value : null);

value が 7 文字より長い値をフィルタリングし、value を返しています。

プログラムの実行結果は次のようになりました。

(○・ω・)ノ————- searchValues ————-
searchValuesResult: Sparkler

Map.Entry による検索

String searchEntriesResult = map.searchEntries(1,
        entry -> entry.getKey().length() > 7 && entry.getValue().length() > 7 ? entry.getKey() + ” = ” + entry.getValue() : null);

Entry の key と value をそれぞれ取得、そして key が 7 文字より長くかつ value が 7 文字より長い値をフィルタリングし、key, value を一つの文字列として返しています。(解りやすいように連結用の文字を追加しています。)

プログラムの実行結果は次のようになりました。

(○・ω・)ノ————- searchEntries ————-
searchEntriesResult: JDK 1.1.4 = Sparkler

reduce

それでは reduce を見てみましょう。

80 行目から 114 行目まで reduce を試しています。

reduce も search 同様に key と value, key, value, Map.Entry それぞれに操作を行うメソッドを用意しています。

API ドキュメントを見てみましょう。

public <U> U reduce(long parallelismThreshold,
                    BiFunction<? super K,? super V,? extends U> transformer,
                    BiFunction<? super U,? super U,? extends U> reducer)

指定されたリデューサを使用して値を結合することにより、すべての(キー、値)ペアの指定された変換の累積結果を返します。結果がない場合はnullを返します。

型パラメータ:
U – トランスフォーマの戻り値の型

パラメータ:
parallelismThreshold – このオペレーションを並列的に実行するために必要な(推定の)要素数
transformer – 要素の変換を返す関数。変換がない場合はnull(その場合、値は結合されない)
reducer – 交換可能性と結合性を持つ結合関数

戻り値:
すべての(キー、値)ペアの指定された変換を累積した結果

public <U> U reduceKeys(long parallelismThreshold,
                        Function<? super K,? extends U> transformer,
                        BiFunction<? super U,? super U,? extends U> reducer)

指定されたリデューサを使用して値を結合することにより、すべてのキーの指定された変換の累積結果を返します。結果がない場合はnullを返します。

型パラメータ:
U – トランスフォーマの戻り値の型

パラメータ:
parallelismThreshold – このオペレーションを並列的に実行するために必要な(推定の)要素数
transformer – 要素の変換を返す関数。変換がない場合はnull(その場合、値は結合されない)
reducer – 交換可能性と結合性を持つ結合関数

戻り値:
すべてのキーの指定された変換を累積した結果

public <U> U reduceValues(long parallelismThreshold,
                          Function<? super V,? extends U> transformer,
                          BiFunction<? super U,? super U,? extends U> reducer)

指定されたリデューサを使用して値を結合することにより、すべての値の指定された変換の累積結果を返します。結果がない場合はnullを返します。

型パラメータ:
U – トランスフォーマの戻り値の型

パラメータ:
parallelismThreshold – このオペレーションを並列的に実行するために必要な(推定の)要素数
transformer – 要素の変換を返す関数。変換がない場合はnull(その場合、値は結合されない)
reducer – 交換可能性と結合性を持つ結合関数

戻り値:
すべての値の指定された変換を累積した結果

public <U> U reduceEntries(long parallelismThreshold,
                           Function<Map.Entry<K,V>,? extends U> transformer,
                           BiFunction<? super U,? super U,? extends U> reducer)

指定されたリデューサを使用して値を結合することにより、すべてのエントリの指定された変換の累積結果を返します。結果がない場合はnullを返します。

型パラメータ:
U – トランスフォーマの戻り値の型

パラメータ:
parallelismThreshold – このオペレーションを並列的に実行するために必要な(推定の)要素数
transformer – 要素の変換を返す関数。変換がない場合はnull(その場合、値は結合されない)
reducer – 交換可能性と結合性を持つ結合関数

戻り値:
すべてのエントリの指定された変換を累積した結果

reduce は、int, long, double などを便利に使えるようにする reduce○△×□ToInt, reduce○△×□ToLong, reduce○△×□ToDouble なども用意されています。

これらは、入力をプリミティブ型に変換して基準値を指定して累積計算して値を返すというものです。

今回のプログラムでは、reduceValuesToInt だけ試しています。

API ドキュメントでは次のようになっています。

public int reduceValuesToInt(long parallelismThreshold,
                             ToIntFunction<? super V> transformer,
                             int basis,
                             IntBinaryOperator reducer)

指定されたリデューサを使用して値を結合し、指定された基準を識別値として使用して、すべての値の指定された変換の累積結果を返します。

パラメータ:
parallelismThreshold – このオペレーションを並列的に実行するために必要な(推定の)要素数
transformer – 要素の変換を返す関数basis – リダクションの識別(初期のデフォルト値)
reducer – 交換可能性と結合性を持つ結合関数

戻り値:すべての値の指定された変換を累積した結果

それではプログラムを見ていきましょう。

key と value によるリデュース

String reduceResult = map.reduce(1,
        (key, value) -> key + ” = ” + value,
        (s1, s2) -> s1 + System.getProperty(“line.separator”) + s2);

第二引数の BiFunction で要素の変換処理(key, value を一つの文字列として返しています。(解りやすいように連結用の文字を追加しています。)をして返し、

第三引数の BiFunction で返された値をシステムの改行文字を加えて結合して返します。

このプログラムの実行結果は次のようになります。

(○・ω・)ノ————- reduce ————-
reduceResult
JDK 1.1.7 = Brutus
Java SE 9 =
JDK 1.1.8 = Chelsea
Java SE 8 = null
JDK 1.1.5 = Pumpkin
JDK 1.1.6 = Abigail
J2SE 1.2 = Playground
JDK 1.1.4 = Sparkler
Java SE 7 = Dolphin
Java SE 6 = Mustang
J2SE 1.2.2 = Cricket
J2SE 1.3.1 = Ladybird
J2SE 1.2.1 = null
J2SE 1.3 = Kestrel
J2SE 1.4 = Merlin
J2SE 5.0 = Tiger
J2SE 1.4.2 = Mantis
J2SE 1.4.1 = Hopper

key によるリデュース

String reduceKeysResult = map.reduceKeys(1,
        key -> key.length() > 9 ? key : null,
        (s1, s2) -> s1 + System.getProperty(“line.separator”) + s2);

第二引数の Function で要素の変換処理 key が 9 文字より長い値をフィルタリングし、key を返し、

第三引数の BiFunction で返された値をシステムの改行文字を加えて結合して返します。

このプログラムの実行結果は次のようになります。

(○・ω・)ノ————- reduceKeys ————-
reduceKeysResult
J2SE 1.2.2
J2SE 1.3.1
J2SE 1.2.1
J2SE 1.4.2
J2SE 1.4.1

value によるリデュース

String reduceValuesResult = map.reduceValues(1,
        value -> value.length() > 4 && value.length() < 7 ? value : null,
        (s1, s2) -> s1 + System.getProperty(“line.separator”) + s2);

第二引数の Function で要素の変換処理 value が 4 文字より長く、かつ value が 7 文字より短い値をフィルタリングし、value を返し、

第三引数の BiFunction で返された値をシステムの改行文字を加えて結合して返します。

このプログラムの実行結果は次のようになります。

(○・ω・)ノ————- reduceValues ————-
reduceValuesResult
Brutus
Merlin
Tiger
Mantis
Hopper

Map.Entry によるリデュース

String reduceEntriesResult = map.reduceEntries(1,
        entry -> entry.getKey().length() > 7 && entry.getValue().length() > 7 ? entry.getKey() + ” = ” + entry.getValue() : null,
        (s1, s2) -> s1 + System.getProperty(“line.separator”) + s2);

第二引数の Function で要素の変換処理 Map.Entry の key と value をそれぞれ取得、そして key が 7 文字より長くかつ value が 7 文字より長い値をフィルタリングし、

Map.Entry の key, value を一つの文字列として返しています。(解りやすいように連結用の文字を追加しています。)

第三引数の BiFunction で返された値をシステムの改行文字を加えて結合して返します。

このプログラムの実行結果は次のようになります。

(○・ω・)ノ————- reduceEntries ————-
reduceEntriesResult
J2SE 1.2 = Playground
JDK 1.1.4 = Sparkler
J2SE 1.3.1 = Ladybird

reduceValuesToInt

int reduceMaxValuesResult = map.reduceValuesToInt(1,
        String::length,
        0,
        Integer::max);

第二引数の ToIntFunction で value の文字列の長さを返し(int 値)、

第三引数の基準値を元に、

第四引数の IntBinaryOperator で最大値を求める累積処理を行い最大値を返します。

このプログラムの実行結果は次のようになります。

(○・ω・)ノ————- reduceMaxValuesResult ————-
10

これで、ざっくり試してみたのでお終いです。

(○・ω・)ノ————- お終い! ————-

今回は試さなかったけど次のような優れものが Java SE 8 から追加されてます。

public static <K> ConcurrentHashMap.KeySetView<K,Boolean> newKeySet()

指定された型からBoolean.TRUEへの、ConcurrentHashMapに連動する新しいSetを作成します。

型パラメータ:
K – 返されるセットの要素型

戻り値:
新しいセット

public static <K> ConcurrentHashMap.KeySetView<K,Boolean> newKeySet(int initialCapacity)

指定された型からBoolean.TRUEへの、ConcurrentHashMapに連動する新しいSetを作成します。

型パラメータ:
K – 返されるセットの要素型

パラメータ:
initialCapacity – 実装は、この多数の要素を格納するように内部のサイズ設定を実行する。

戻り値:
新しいセット

例外:
IllegalArgumentException – 要素の初期容量が負の場合

Java SE 8 から Map が強力になった。

特に ConcurrentHashMap は、J2SE5.0 の時と比べるとかなり使い勝手がよくなった。(^_^)

さて、ここでもう一度 ConcurrentHashMap の特徴を思い出してみましょう。

ConcurrentHashMap は、すべてのメソッドを共通のロックで同期化してアクセスを一度に一つのスレッドに限定する代わりに、

ロックストライピングという小さな粒度のロック方式を採用することにより並行処理能力を高めています。

ConcurrentHashMap は、Iterator および Enumeration は、ある時点または反復子/列挙の作成以降のハッシュテーブルの状態を反映する要素を返すように設計されているため、

ConcurrentModificationException をスローすることはありません。

なにより興味深いのはデータの取得時にはブロックすることがなく、更新についてはユーザーが並行レベルを設定できるということです。

ちょっと最後の並行レベルを設定できると言うところを試してみました。

ConcurrentHashMap インスタンスを生成するコンストラクタは 5 種類あります。

その中で並行レベルを設定できるのは次の引数を持つコンストラクタです。

public ConcurrentHashMap(int initialCapacity,
                         float loadFactor,
                         int concurrencyLevel)

指定された要素数(initialCapacity)、テーブル密度(loadFactor)および並行更新数のしきい値(concurrencyLevel)に基づく初期テーブル・サイズで、新しい空のマップを作成します。

パラメータ:
initialCapacity – 初期容量。負荷係数を指定すると、実装はこの数の要素を格納できるように内部のサイズ設定を行う。
loadFactor – 初期テーブル・サイズを設定するための負荷係数(テーブル密度)
concurrencyLevel – 並行して更新しているスレッドの推定数。実装はこの値をサイズ設定のヒントとして使用できる。

例外:
IllegalArgumentException – 初期容量が負であるか、負荷係数またはconcurrencyLevelが正でない場合

第三引数の int concurrencyLevel が並行レベルにあたります。

Java SE 8 の API ドキュメントでは初期容量、負荷係数、並行レベルのデフォルト値が明記されなくなりました。(初期容量は 16 とありました)

ソースを覗いてみると初期容量 16, 負荷係数 0.75f, 並行レベル 16 となっています。昔と変わって無さそうです。

並行レベルの設定で大きな違いがでるのかつぎのような ConcurrentHashMap インスタンスを生成して試してみました。

ConcurrentHashMap<Long, Long> map = new ConcurrentHashMap<>();
ConcurrentHashMap<Long, Long> map2 = new ConcurrentHashMap<>(16, 0.75f, 1);

下記 udDate() メソッドを同時に実行するスレッドを 32 個作って試してみました。

CompletableFuture<Void> cnt_1 = CompletableFuture.runAsync(() -> upDate.upDate()); こんなのを 32 個 (^_^;)

void upDate() {
    for (long i = 0; i < 100_000_000; i++) {
        map.computeIfAbsent(i, (k) -> k);
        if ((i & 0b1) == 0) {
            map.remove(i);
        }
    }
    for (long i = 0; i < 100_000_000; i++) {
        map.computeIfAbsent(i, (k) -> k * 10);
        map.getOrDefault(i, – 1L);
    }
}

プログラムの実行結果は次のようになりました。

ConcurrentHashMap<Long, Long> map = new ConcurrentHashMap<>(); // 5分33秒14428361700004189

ConcurrentHashMap<Long, Long> map2 = new ConcurrentHashMap<>(16, 0.75f, 1); // 34分42秒4334219810002651

凄く大差がつきました。

ちなみに実行環境は、先ほどのプログラムを実行しているコンピュータで論理 CPU 数 32 個、VM オプションの ForkJoinPool の制限無しで試してみました。

以上、ConcurrentHashMap でのお遊びはこれでお終いです。

Hatena タグ:

java.util.ConcurrentModificationException 対処法

Java

今日のエントリーも J2SE5.0 時代の古いものです。

しかし、あまり知られていない(目にする機会が少ない)と思われるのでブログ更新用のネタとしました。

今回の問題は二つのスレッドが並行して List オブジェクトにアクセスするよくある問題です。

ここで思い出してください。

Java の Iterator は Fail-Fast に設計されています。

これはイテレーション処理開始後にコレクションの内容が変更されたことを検出したら直ち java.util.ConcurrentModificationException をスローします。

これを回避するには一番手っ取り早いのは synchronized ブロックを使って制御するのが楽です。

synchronizedList() を使ってるから大丈夫だと思っている人は泣いてください。(ヲヒ

Iterator なめたらあかんぜよ!

さて、いったい何を言いたいのか良く解らないと思うので簡潔にまとめていないダラダラとしたサンプルコードをご覧ください。

このプログラムの実行結果は次のような実行時エラーがでます。

堀北真希 Girls_1
北川景子 Girls_1
堀北真希 Girls_2
Exception in thread “main” java.util.concurrent.CompletionException: java.util.ConcurrentModificationException
    at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
    at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1584)
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1574)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1689)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.util.ConcurrentModificationException
    at java.util.ArrayList.forEach(ArrayList.java:1252)
    at jp.yucchi.girlfriends_cme.GirlFriends_1.updateGirlFriend(GirlFriends_1.java:27)
    at jp.yucchi.girlfriends_cme.GirlFriends_CME.lambda$main$0(GirlFriends_CME.java:26)
    at jp.yucchi.girlfriends_cme.GirlFriends_CME$$Lambda$1/424058530.get(Unknown Source)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1582)
    … 5 more
Java Result: 1

このプログラムは、GirlFriends_1 スレッドで要素を追加し、イテレーション処理を要素を一つ出力させるごとに一秒間スレッドをスリープさせています。

GirlFriends_2 スレッドでは、要素の削除処理の開始を一秒遅らせています。

GirlFriends_1 スレッドのイテレーション処理中に  GirlFriends_2 スレッド によって List の内容が変更されてしまいます。

二つのスレッドで一つの List に何も考えずに並行処理している無残な結果が悲しいですね。

では、安全に並行処理するために synchronized ブロック を使って終わりではつまらないのでもう一つの解決方法を紹介します。

これを使う機会って滅多にないでしょうけど java.util.concurrent.CopyOnWriteArrayList<E> ってのがあります。

API ドキュメントには次のように書かれています。

基になる配列の新しいコピーを作成することにより、すべての推移的操作(add、setなど)が実装されるArrayListのスレッド・セーフな変数です。
通常、これは非常に効率が悪いのですが、トラバーサル操作が変更を数の点で大幅に上回る場合には、代替手段よりも効率が良い場合があります。
また、これは、トラバーサルを同期できない場合や、同期することを望まないが、並行スレッド間の干渉を排除する必要がある場合に有用です。
「スナップショット」スタイルのイテレータ・メソッドは、イテレータの作成時点での配列状態への参照を使用します。
この配列がイテレータの有効期間中に変更されることは決してないため、干渉は不可能であり、イテレータはConcurrentModificationExceptionをスローしないことが保証されます。
イテレータは、イテレータの作成以降のリストへの追加、削除、または変更を反映しません。
イテレータ自体に対する要素変更操作(remove、setおよびadd)はサポートされません。
これらのメソッドは、UnsupportedOperationExceptionをスローします。
nullを含むすべての要素が許可されます。
メモリー整合性効果: ほかの並行処理コレクションと同様、オブジェクトをCopyOnWriteArrayListに配置する前のスレッド内のアクションは、
別のスレッドでのその要素へのアクセスまたはCopyOnWriteArrayListからの削除に続くアクションよりも前に発生します。

今回のケースではお手軽に使えそうですね。

そう言うことでまたダラダラとコードをご覧ください。と、思ったけど List を CopyOnWriteArrayList に変更するだけなのでやめておきます。

いちおう、 GitHub にアップしておきますので見たい方はどうぞ。

https://github.com/Yucchi-1995/GirlFriends

プログラムの実行結果は次のようになります。

堀北真希 Girls_1
北川景子 Girls_1
堀北真希 Girls_2
桐谷美玲 Girls_1
新垣結衣 Girls_1
剛力彩芽 Girls_1
GirlFriend_1 List
堀北真希
綾瀬はるか
GirlFriend_2 List
堀北真希
綾瀬はるか
GirlFriend List
堀北真希
綾瀬はるか
新垣結衣
剛力彩芽

java.util.ConcurrentModificationException が投げられることなく並行処理が完了しています。

とても簡単です。

API ドキュメントにも書いてありますが CopyOnWriteArrayList はイテレータの作成時点での配列状態への参照を使用し、

この配列がイテレータの有効期間中に変更されることは決してないと言い切っているとおり、

プログラムの実行結果より Girls_1 スレッド、Girls_2 スレッドのイテレーション処理出力がそのとおりに出力されているのが確認できます。

堀北真希 Girls_1
北川景子 Girls_1
堀北真希 Girls_2
桐谷美玲 Girls_1
新垣結衣 Girls_1
剛力彩芽 Girls_1

そして、Girls_1 スレッド、Girls_2 スレッドから返される List の要素は「堀北真希」の一つとなります。

GirlFriend_1 List、GirlFriend_2 List の出力が

GirlFriend_1 List
堀北真希
綾瀬はるか
GirlFriend_2 List
堀北真希
綾瀬はるか

となっているのは main スレッドに戻ってから「綾瀬はるか」が追加され出力されているからです。

そして、最後に「新垣結衣」と「剛力彩芽」を追加して List を表示させています。

CopyOnWriteArrayList って本当に優れものですね!

どうでもいいことですが、このプログラムの欠陥は「北川景子」を削除して、あとで再び List に追加する予定だったのをすっかり忘れてしまったことです。(^_^;)

Hatena タグ:

食事する哲学者の問題(Dining Philosophers Problem) Semaphore で遊ぶ その2

Java NetBeans

食事する哲学者の問題(Dining Philosophers Problem)を J2SE5.0 で追加された java.util.concurrent.Semaphore を使ってプログラムを組んでみます。

Semaphore については前回のエントリーでざっくり説明し、シンプルなプログラムも紹介しています。

Semaphore で遊ぶ

それでは、食事する哲学者の問題(Dining Philosophers Problem)とは何か調べてみます。

WikiPedia によると、食事する哲学者の問題(Dining Philosophers Problem)とは、並列処理に関する問題を一般化した例である。古典的なマルチプロセスの同期(排他制御)問題である。とされています。

もともとは、1965年、エドガー・ダイクストラは5台のコンピュータが5台のテープ装置に競合アクセスするという同期問題を提示した。

間もなく、この問題はアントニー・ホーアによって「食事する哲学者の問題」に変形して語られることとなった。

どうして食事する哲学者にすり替わったかは置いといて・・・

【問題】

5人の哲学者が食事したり、考え事をしたりしている。

かれらの前には、真ん中にスパゲッティの入った大きなボールが置かれた丸い食卓がある。

その食卓には5枚の皿が置かれ、皿と皿の間にフォークが1本ずつ置かれている。

スパゲッティをボールから取って自分の皿によそうには2本のフォークを必要とし、哲学者は一度に1本のフォークしかテーブルから取ることができない。

(左右の手で同時に両側にある2本のフォークを取ることはできない、という意味。まずどちらかの側のフォークを取ってから、逆の側のフォークを取る。)

哲学者同士は決して会話をしない。

このため、5人が左のフォークを手にとって右のフォークが食卓に置かれるのを待つという危険なデッドロック状態が発生する可能性がある。

スパゲッティではないけどこういう状況ですね。

cake1

【解説】

本来、デッドロック問題の解説手段として使われた。このシステムがデッドロックに到るのは「満たされない要求の円環」が存在する場合である。

例えば、哲学者 P1 が哲学者 P2 の手にしているフォークを待っていて、P2 は哲学者 P3 のものを……といったように円環形に要求が連鎖する。

例えば、それぞれの哲学者が次のように振る舞うとする。

1.左のフォークが得られるまで思索して待ち、フォークを取り上げる。

2.右のフォークが得られるまで思索して待ち、フォークを取り上げる。

3.食事する。

4.右のフォークを置く。

5.左のフォークを置く。

6.最初にもどって繰り返す。

この解法は正しくない。

このやりかたではデッドロックが発生し、全哲学者が左のフォークを持ち、右のフォークが持てるようになるのを待つという状況が発生しうる。

その場合、右のフォークは決して得られない (>_<。)

今回はこの方法でデッドロックを起こさないように Semaphore を使ってリソースへのアクセスを制限し対処するプログラムを組んでみることにします。

これって少し考えてみれば食事をしている両隣の哲学者はフォークをとれない状況ができるので同時に食事出来る哲学者の最大数は5人の場合は二人となります。

つまり、使われるフォークの数は4本となります。

これを利用すれば比較的簡単に Semaphore を利用してプログラムを組むことができますね。

それではプログラムを組んでみることにしますが、素直に哲学者に登場していただくより綺麗な女性を食事にお誘いしました。

今回デッドロックを避けるために使う Semaphore はウェイターの役割をはたします。

つまり、スパゲッティを食べる権利( パーミット取得 )があることを許可してくれます。

綺麗な女性は左のフォーク、そして右のフォークと使うことができればスパゲッティを食べることができます。

ただし、隣の女性がフォークを使っていたら( ロックがかかっていたら )フォークがあくのを待たなければ( ロックが解放される )いけません。

ウエイターは綺麗な女性がスパゲッティを食べ終わり左右のフォークを置いたなら、

その綺麗な女性のスパゲッティを食べる権利( パーミット )を戻してもらい、

次に待機している綺麗な女性がいれば「どうぞ、お食べください」とスパゲッティを食べる権利( パーミット )を与えます。。

そして綺麗な女性はフォークが空いていれば手に取りスパゲッティを食べられます。

フォークが空いていなければ待ちます。

これの繰り返しです。

最大二人の綺麗な女性がスパゲッティを同時に食べることがこれで可能となります。

左右のフォークの使用状況( ロックの状態 )によりフォーク待ち時間が発生してしまいますがこれでデッドロックは解消されます。

きっと綺麗な女性達は美味しいスパゲッティをお腹いっぱい食べて満足してお帰りになられたでしょう。

これをプログラムにすると次のようになりました。

プログラムを実行させてデッドロックが発生せず動作するか確認します。

桐谷美玲 左のフォークをとりあげる思索した時間は、0秒7838E-5
北川景子 左のフォークをとりあげる思索した時間は、0秒8168E-5
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒313E-6
桐谷美玲 1回目の食事
桐谷美玲 1回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、1秒3359217400000001
北川景子 1回目の食事
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
北川景子 1回目食事終了
剛力彩芽 右のフォークをとりあげる思索した時間は、1秒3721449000000001
剛力彩芽 1回目の食事
堀北真希 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
堀北真希 1回目の食事
剛力彩芽 1回目食事終了
堀北真希 1回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒375070565
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒652E-6
桐谷美玲 2回目の食事
新垣結衣 1回目の食事
新垣結衣 1回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒652E-6
桐谷美玲 2回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、0秒08200188600000001
北川景子 2回目の食事
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒652E-6
北川景子 2回目食事終了
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒583391387
堀北真希 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒652E-6
堀北真希 2回目の食事
剛力彩芽 2回目の食事
堀北真希 2回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒652E-6
剛力彩芽 2回目食事終了
新垣結衣 右のフォークをとりあげる思索した時間は、0秒436658855
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒652E-6
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒652E-6
新垣結衣 2回目の食事
桐谷美玲 3回目の食事
新垣結衣 2回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒294E-6
桐谷美玲 3回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、0秒23805942200000002
北川景子 3回目の食事
堀北真希 左のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
堀北真希 3回目の食事
北川景子 3回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒312E-6
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
剛力彩芽 3回目の食事
堀北真希 3回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒652E-6
剛力彩芽 3回目食事終了
新垣結衣 右のフォークをとりあげる思索した時間は、0秒18132323900000002
新垣結衣 3回目の食事
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒652E-6
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 4回目の食事
新垣結衣 3回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 4回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、0秒39374600600000004
北川景子 4回目の食事
堀北真希 左のフォークをとりあげる思索した時間は、0秒652E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒312E-6
堀北真希 4回目の食事
堀北真希 4回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒9640000000000005E-6
北川景子 4回目食事終了
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒023616073
剛力彩芽 4回目の食事
新垣結衣 左のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
剛力彩芽 4回目食事終了
新垣結衣 右のフォークをとりあげる思索した時間は、0秒7176410350000001
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒312E-6
新垣結衣 4回目の食事
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 5回目の食事
桐谷美玲 5回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
新垣結衣 4回目食事終了
堀北真希 右のフォークをとりあげる思索した時間は、0秒718831517
堀北真希 5回目の食事
北川景子 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
北川景子 右のフォークをとりあげる思索した時間は、0秒3543E-5
北川景子 5回目の食事
堀北真希 5回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒6430000000000004E-6
北川景子 5回目食事終了
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒140569047
剛力彩芽 5回目の食事
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒981E-6
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 6回目の食事
桐谷美玲 6回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒6420000000000003E-6
剛力彩芽 5回目食事終了
新垣結衣 右のフォークをとりあげる思索した時間は、0秒256017732
新垣結衣 5回目の食事
堀北真希 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
新垣結衣 5回目食事終了
堀北真希 右のフォークをとりあげる思索した時間は、0秒908995176
堀北真希 6回目の食事
北川景子 左のフォークをとりあげる思索した時間は、0秒973E-6
北川景子 右のフォークをとりあげる思索した時間は、0秒313E-6
北川景子 6回目の食事
堀北真希 6回目食事終了
北川景子 6回目食事終了
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒156232277
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒652E-6
桐谷美玲 7回目の食事
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
剛力彩芽 6回目の食事
桐谷美玲 7回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒312E-6
剛力彩芽 6回目食事終了
新垣結衣 右のフォークをとりあげる思索した時間は、0秒200991297
新垣結衣 6回目の食事
堀北真希 左のフォークをとりあげる思索した時間は、0秒652E-6
新垣結衣 6回目食事終了
堀北真希 右のフォークをとりあげる思索した時間は、1秒3822943870000002
堀北真希 7回目の食事
北川景子 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
北川景子 右のフォークをとりあげる思索した時間は、0秒3030000000000004E-6
北川景子 7回目の食事
堀北真希 7回目食事終了
北川景子 7回目食事終了
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒089658099
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 8回目の食事
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒625000000000001E-6
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒313E-6
剛力彩芽 7回目の食事
剛力彩芽 7回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒955E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒652E-6
新垣結衣 7回目の食事
桐谷美玲 8回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
新垣結衣 7回目食事終了
堀北真希 右のフォークをとりあげる思索した時間は、0秒8264462760000001
北川景子 左のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
堀北真希 8回目の食事
北川景子 右のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
北川景子 8回目の食事
堀北真希 8回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒6420000000000003E-6
北川景子 8回目食事終了
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒217371327
剛力彩芽 8回目の食事
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒312E-6
桐谷美玲 9回目の食事
剛力彩芽 8回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
新垣結衣 8回目の食事
桐谷美玲 9回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
新垣結衣 8回目食事終了
堀北真希 右のフォークをとりあげる思索した時間は、1秒21894861600000004
北川景子 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
堀北真希 9回目の食事
北川景子 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
北川景子 9回目の食事
堀北真希 9回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒652E-6
北川景子 9回目食事終了
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒780406594
剛力彩芽 9回目の食事
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒294E-6
桐谷美玲 10回目の食事
剛力彩芽 9回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒973E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒652E-6
新垣結衣 9回目の食事
桐谷美玲 10回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
新垣結衣 9回目食事終了
堀北真希 右のフォークをとりあげる思索した時間は、0秒475458529
堀北真希 10回目の食事
北川景子 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
北川景子 右のフォークをとりあげる思索した時間は、0秒312E-6
北川景子 10回目の食事
堀北真希 10回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒652E-6
北川景子 10回目食事終了
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒34860347900000005
新垣結衣 左のフォークをとりあげる思索した時間は、0秒652E-6
剛力彩芽 10回目の食事
剛力彩芽 10回目食事終了
新垣結衣 右のフォークをとりあげる思索した時間は、1秒0036094330000000507
新垣結衣 10回目の食事
新垣結衣 10回目食事終了

堀北真希 が 10回食事するのに要した時間は、28秒6504769030000013でした。
新垣結衣 が 10回食事するのに要した時間は、30秒9585105100000035でした。
剛力彩芽 が 10回食事するのに要した時間は、30秒0028747960000004014でした。
北川景子 が 10回食事するのに要した時間は、28秒998852129000003でした。
桐谷美玲 が 10回食事するのに要した時間は、27秒3586783140000023でした。

(○・ω・)ノ——– I eat delicious spaghetti and am stuffed. ——–(^_^)

ちゃんと動きました。(^_^)

このプログラムでは Semaphore インスタンス生成時のコンストラクタ引数に哲学者の人数を割り当てました。

また、公平性も設定しました。

パーミット数の設定は同時アクセスは 2 スレッドまでなので 2 と設定して、acquire()、release() とパーミット取得数とパーミット解放数を 1 してもいいでしょう。

今回は、パーミット数の設定を 5 , acquire(2)、release(2) とパーミット取得数とパーミット解放数を 2 としました。

同時アクセス可能なのが 2 スレッドで左右のフォークをロックするのでこの問題の鍵となるフォークの数を考慮にいれました。(あまり意味の無いこだわり)

final Semaphore semaphore = new Semaphore(PHILOSOPHERS_NUMBER, true);

NetBeans のプロファイラでスレッドの状態を確認してみましょう。

1

5 個の ForkJoinPool.commonPool-worker-29 とかのスレッドが最大で二つ同時アクセスされているのが確認できます。

スレッド終了後にフォークのロック解放待ちが無い場合はすぐに新たなスレッドが実行されているのも確認できます。

Semaphore って本当に便利ですね!

さて、ここでちょっとしたことを試してみよう。

Semaphore の公平性の設定をしなかったらどうなるのか?

final Semaphore semaphore = new Semaphore(PHILOSOPHERS_NUMBER); // 公平性無し

では、プログラムを実行させてみます。

剛力彩芽 左のフォークをとりあげる思索した時間は、0秒0810000000000002E-5
堀北真希 左のフォークをとりあげる思索した時間は、0秒0810000000000002E-5
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
剛力彩芽 1回目の食事
堀北真希 1回目の食事
堀北真希 1回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒313E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒652E-6
堀北真希 2回目の食事
剛力彩芽 1回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒312E-6
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒652E-6
剛力彩芽 2回目の食事
堀北真希 2回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒312E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒652E-6
堀北真希 3回目の食事
剛力彩芽 2回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒910000000000001E-7
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
剛力彩芽 3回目の食事
堀北真希 3回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
堀北真希 4回目の食事
剛力彩芽 3回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒910000000000001E-7
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
剛力彩芽 4回目の食事
堀北真希 4回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒652E-6
堀北真希 5回目の食事
剛力彩芽 4回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒6420000000000003E-6
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
剛力彩芽 5回目の食事
堀北真希 5回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒312E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
堀北真希 6回目の食事
剛力彩芽 5回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒910000000000001E-7
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒652E-6
剛力彩芽 6回目の食事
剛力彩芽 6回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒312E-6
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
剛力彩芽 7回目の食事
堀北真希 6回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒910000000000001E-7
堀北真希 右のフォークをとりあげる思索した時間は、0秒652E-6
堀北真希 7回目の食事
剛力彩芽 7回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
剛力彩芽 8回目の食事
堀北真希 7回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒910000000000001E-7
堀北真希 右のフォークをとりあげる思索した時間は、0秒652E-6
堀北真希 8回目の食事
剛力彩芽 8回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒973E-6
剛力彩芽 9回目の食事
堀北真希 8回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒6430000000000004E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
堀北真希 9回目の食事
剛力彩芽 9回目食事終了
剛力彩芽 左のフォークをとりあげる思索した時間は、0秒652E-6
剛力彩芽 右のフォークをとりあげる思索した時間は、0秒652E-6
剛力彩芽 10回目の食事
堀北真希 9回目食事終了
堀北真希 左のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
堀北真希 右のフォークをとりあげる思索した時間は、0秒652E-6
堀北真希 10回目の食事
剛力彩芽 10回目食事終了
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒312E-6
堀北真希 10回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒616E-6
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒830071894
桐谷美玲 1回目の食事
桐谷美玲 1回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、0秒888391644
北川景子 1回目の食事
北川景子 1回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒6420000000000003E-6
桐谷美玲 左のフォークをとりあげる思索した時間は、1秒44702734600000005
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒652E-6
桐谷美玲 2回目の食事
桐谷美玲 2回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、1秒2932113780000001
北川景子 2回目の食事
北川景子 2回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒3030000000000004E-6
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒6195947270000001
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒312E-6
桐谷美玲 3回目の食事
桐谷美玲 3回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、1秒23741232
北川景子 3回目の食事
北川景子 3回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒6420000000000003E-6
桐谷美玲 左のフォークをとりあげる思索した時間は、1秒2788060170000002
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒652E-6
桐谷美玲 4回目の食事
桐谷美玲 4回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、0秒9319671190000001
北川景子 4回目の食事
北川景子 4回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒633E-6
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒55758825
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒3543E-5
桐谷美玲 5回目の食事
桐谷美玲 5回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、0秒7469512070000001
北川景子 5回目の食事
北川景子 5回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒910000000000001E-7
桐谷美玲 左のフォークをとりあげる思索した時間は、1秒08432603500000013
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒973E-6
桐谷美玲 6回目の食事
桐谷美玲 6回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、0秒733817256
北川景子 6回目の食事
北川景子 6回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 左のフォークをとりあげる思索した時間は、1秒1698336330000001
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒312E-6
桐谷美玲 7回目の食事
桐谷美玲 7回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、1秒4533695360000001
北川景子 7回目の食事
北川景子 7回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒322E-6
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒8525430700000001
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒652E-6
桐谷美玲 8回目の食事
桐谷美玲 8回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、0秒669598278
北川景子 8回目の食事
北川景子 8回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 左のフォークをとりあげる思索した時間は、1秒3454052980000002
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
桐谷美玲 9回目の食事
桐谷美玲 9回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、1秒25630280000000005
北川景子 9回目の食事
北川景子 9回目食事終了
北川景子 左のフォークをとりあげる思索した時間は、0秒652E-6
桐谷美玲 左のフォークをとりあげる思索した時間は、0秒598060677
桐谷美玲 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
桐谷美玲 10回目の食事
桐谷美玲 10回目食事終了
北川景子 右のフォークをとりあげる思索した時間は、1秒09184714699999996
北川景子 10回目の食事
新垣結衣 左のフォークをとりあげる思索した時間は、0秒946E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒312E-6
新垣結衣 1回目の食事
北川景子 10回目食事終了
新垣結衣 1回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒973E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
新垣結衣 2回目の食事
新垣結衣 2回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒6430000000000004E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
新垣結衣 3回目の食事
新垣結衣 3回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒312E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒652E-6
新垣結衣 4回目の食事
新垣結衣 4回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒322E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
新垣結衣 5回目の食事
新垣結衣 5回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒024E-5
新垣結衣 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
新垣結衣 6回目の食事
新垣結衣 6回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒973E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
新垣結衣 7回目の食事
新垣結衣 7回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒6430000000000004E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒9820000000000003E-6
新垣結衣 8回目の食事
新垣結衣 8回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒652E-6
新垣結衣 右のフォークをとりあげる思索した時間は、0秒3210000000000001E-6
新垣結衣 9回目の食事
新垣結衣 9回目食事終了
新垣結衣 左のフォークをとりあげる思索した時間は、0秒910000000000001E-7
新垣結衣 右のフォークをとりあげる思索した時間は、0秒6510000000000002E-6
新垣結衣 10回目の食事
新垣結衣 10回目食事終了

堀北真希 が 10回食事するのに要した時間は、10秒9469584730000005でした。
新垣結衣 が 10回食事するのに要した時間は、40秒20581136300000225でした。
剛力彩芽 が 10回食事するのに要した時間は、10秒11629695400000095でした。
北川景子 が 10回食事するのに要した時間は、31秒21609595200000342でした。
桐谷美玲 が 10回食事するのに要した時間は、30秒20136357100000168でした。

(○・ω・)ノ——– I eat delicious spaghetti and am stuffed. ——–(^_^)

うわぁぁぁ・・・ 思いっきり不公平じゃないですか!

NetBeans のプロファイラでスレッドの状態を確認してみます。

2

今回のようなフォークのロック解放待ちがあるような場合はまずいような・・・

こんなとこもあるよね。

3

プログラムの処理時間が大幅に増えてます。

公平性の設定はよく考えないと思わぬ結果をもたらすかもしれませんね。

CompletableFuture を使えば楽に並行処理を記述できるし、Java って世に出たときはマルチスレッド対応だぜ!って誇らしげに売りにしていた。

あれから20年、確実に進化しています。

どんどん進化していく並行処理を日本語で優しく解説しているサイトはあまり無いのが残念です。

便利なものほど正しく使わないと痛い目をみますからね。

もっと正しい知識を優しい日本語の解説で入手したい今日この頃です。

今月の私は健康診断でコレステロールと中性脂肪の値が高いと指摘されたにもかかわらずこういった美味しいケーキをたくさん食べてしまいました。

c4

c3

c2

c5

美味しさそのままで中性脂肪とコレステロールの値をさげるケーキって無いものか。

お終い!

Hatena タグ:

« 古い記事 新しい記事 »