Flyweight パターンとConcurrentHashMap を Java で学ぶ

Java の知識をアップデートしようとして、Essential Java の第三刷を読んでいるのだが、`Flyweight` パターンが出てきた。そういえばこのパターンはちゃんとやったことがないので、理解して試してみることにした。

# Flyweight パターン
Flyweight パターンは、GoFのデザインパターンの一つで、次のような問題を解くためのパターンである。

* 多くのオブジェクトが効率的にサポートされる
* 多くのオブジェクトの生成を避けたい

## 定義

* 本質的なステートがシェアできる
* (本質的なステートを区別できる）ステートを投入するインターフェイスがある。

何かこれだけだとよくわからない。

* [Flyweight Pattern](https://en.wikipedia.org/wiki/Flyweight_pattern)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/56425584-dd0b-a68d-6c66-e21a2085e5e6.png)

シーケンスを見ると、どうやらキャッシュするパターンのようだ。歴史の項目を見てみると、ドキュメントエディタで使われたようで、フォントの情報とか、例えばアルファベットが２６文字だとすると、その都度文字のインスタンスを生成していたら、インスタンスが大量に生成されてしまう。だから、２６個だけインスタンスを生成するようにして、それをキャッシュして返す仕組みのようだ。


# ギターフレットの音のサンプル

サンプルを作って理解してみよう。要はファクトリーのキャッシュしたものであるので、簡単だ。今回のお題は若干強引だが、ギターの弦の番号と、フレットの番号を入力すると、その場所の音を返すアプリケーションだ。音は１２個しかないので、インスタンスは、１２個でよいはず。デザインパターンのダイアグラムでいう　`Flyweight` のオブジェクトとして、`Sound` を使ってみる。

_Sound.java_ : Flyweight

```java
package com.company;

public interface Sound {
    void Play();
}
```

_SoundImpl_ : Flywieght1

```java
package com.company;

public class SoundImpl implements Sound{
    private String note;
    public SoundImpl(String note) {
        this.note = note;
    }
    @Override
    public void Play() {
        System.out.println(note + "- ♪");
    }
    public String getNote() {
        return note;
    }
}

```

_SoundFactory_ : FlywieghtFactory

```java
package com.company;

import java.util.HashMap;

public class SoundFactory {
    private static Map<String, Sound> sounds = new HashMap<String, Sound>();
    public static Sound getSound(String note) {
        if (sounds.containsKey(note)){
            return sounds.get(note);
        } else {
            Sound sound = new SoundImpl(note);
            sounds.put(note, sound);
            return sound;
        }
    }
}

```

Main が膨れ上がってかっこ悪いが、ともかく、動作するものが出来た。単純でポイントは、Factoryに Map を持たせて、キャッシュしておけば良い。簡単だ。

_Main.java_

```java
package com.company;

import java.util.HashMap;

public class Main {
    private static HashMap<Integer, Integer> stringMap = new HashMap<Integer, Integer>();
    private static HashMap<Integer, String> notes = new HashMap<Integer, String>();
    private static void setup() {
        stringMap.put(1, 4);
        stringMap.put(2, 11);
        stringMap.put(3, 7);
        stringMap.put(4, 2);
        stringMap.put(5, 9);
        stringMap.put(6, 4);

        notes.put(0, "C");
        notes.put(1, "C#");
        notes.put(2, "D");
        notes.put(3, "D#");
        notes.put(4, "E");
        notes.put(5, "F");
        notes.put(6, "F#");
        notes.put(7, "G");
        notes.put(8, "G#");
        notes.put(9, "A");
        notes.put(10, "A#");
        notes.put(11, "B");

    }
    public static void main(String[] args) {
        setup();
	    while(true) {
	        java.io.Console con = System.console();
	        if (con != null) {
                String input = con.readLine("string:fret:");
                if (input.contains("exit")) {
                    System.out.println("Closing ...");
                    System.exit(0);
                } else {
                    String[] stringFret = input.split(":");
                    int openNote = stringMap.get(Integer.parseInt(stringFret[0]));
                    int fret = Integer.parseInt(stringFret[1]);
                    int note = openNote + fret;
                    if (note >= 12) {
                        note = note - 12;
                    }
                    String noteString =  notes.get(new Integer(note));
                    Sound sound = SoundFactory.getSound(noteString);
                    sound.Play();
                }
            }

        }
    }

}

```

# Concurrent だとどうなるのか？

本編が簡単だったので、Javaにあまり慣れていないので、もうステップやってみることにした。コンカレントな状態だと、どうすればいいのだろう？`HashMap` は、Thread Safe ではないらしい。Thread Safe にしたかったら `HashTable` もしくは、`ConcurrentHashMap` を使うと良いらしい。いったい何が違うのだろうか？

* [Difference between HashMap and ConcurrentHashMap in Java Collection](https://www.java67.com/2012/08/difference-between-hashmap-and-concurrentHashMap-java-collection.html)
こちらの記事を元に整理してみたい。

## HashTable と ConcurrentHashMap
* `HashMap` と `ConcurrentHashMap` の違いは、スレッドセーフか否か。つまりコンカレントの環境で使えるか否か。`HashTable` が提供するレベルの同期レベルを提供できるではないが、実用では十分なれべる
* `HashMap` は、`Collections.synchronizedMap` でラップすると`HashTable`とほぼ同等のコレクションが返される。それは、すべての更新イベントで、`Map` 全体がロックされる。`ConcurrentHashMap` の場合は、スレッドセーフを全体のMapをパーティション分割して、一部だけロックするような仕組みになっている。
* `ConcurrentHashMap` は、よりスケーラブルで、パフォーマンスも`Synchronized HashMap` より良い。シングルスレッドの環境下では、`HashMap` の方が、`ConcurrentHashMap` よりパフォーマンスが良い

これらの比較を見てみると、明らかに `ConcurrentHashMap` を使うのが妥当なケースが多そうだ。じゃあ実装してみよう。

# Micronaut を使って実装する

* [MicronautでHello World](https://qiita.com/charon/items/e1cdbbf05a08e8d5ea21)

簡単そうだ。

## Micronaut install

私の環境では、`chocolatey` のソースが書き換わっていたので、デフォルトに戻す。 Administrator 権限で PowerShellを起動するとインストールできる。

```powershell
$ choco isntall micronaut -s https://chocolatey.org/api/v2/
```

* [Cholocatey command source](https://chocolatey.org/docs/commands-sources)

## Generate project via template

次の感じで Maven のプロジェクトを生成できる。

```powershell
$ mn create-app flyweight-server --build maven
```

まずはこれで、サーバーは上がるようになる。自分がハマったポイントは、自分は、IntelliJ を使ってコードを書くのだが、Terminal の JAVA_HOME が設定されていなくて、`Caused by: java.lang.IllegalArgumentException: invalid target release: 11` というエラーメッセージが出た。もともとは、Java8 の JDK だけパスにあったので、それが問題なのだが、Java の基本は、JAVA_HOME の設定と、%JAVA_HOME%/bin をパスに通すことだと改めて思いだした。そうでないと、ちゃんと maven が動作しない。

```powershell
$ cd flywieght-server
$ mvn clean package
$ java -jar .\target\flyweight-server-0.1.jar
```

##　コントローラの追加

コントローラを追記してみる。Micronautは、どちらかというと、Spring の軽量版みたいな感じで様々な機能をサポートしている様子。私は今回 Http Server が欲しいだけだったので、マニュアルを見てコードを書いてみる。コントローラーとルーディングをするのは、単にコントローラを書けばよいみたい。とても簡単でござる。

*[The HTTP Server](https://docs.micronaut.io/1.0.4/guide/index.html#httpServer)

_FlyweightController.java_

```java
package flyweight.server;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/flyweight")
public class FlyweightController {
    @Get(value = "/{string}/{fret}", produces = MediaType.TEXT_PLAIN)
    public String index(Integer string, Integer fret){
        return "String: " + string + " Fret: "+ fret;
    }
}
```

これで動いたので、コントローラーの知りたい挙動は理解できた。次に Flyweight のアプリを改造してみよう。これは、マルチスレッドで動作すると思うので、先ほどの `ConcurrentHashMap` を使ってみよう。

なんと先ほどのより短くなってしまったではないか。`computeIfAbsent` メソッドが、関数を受け取るようになってきて、アトミックな動作を保証してくれる。これは、C# の ConcurrentDictionary と同じ雰囲気だ。だから、もし、なかったら新たに作って返すし、そうでなければ、既存のものを返してくれる。最高！

_SoundFactory.java_

```java
package flyweight.server;

import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class SoundFactory {
    private static ConcurrentMap<String, Sound> sounds = new ConcurrentHashMap<String, Sound>();
    public static Sound getSound(String note) {
        return sounds.computeIfAbsent(note, n -> new SoundImpl(n));
    }
}
```

全体像

_FlyweightController.java_

```java
package flyweight.server;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/flyweight")
public class FlyweightController {
    @Get(value = "/{string}/{fret}", produces = MediaType.TEXT_PLAIN)
    public String index(Integer string, Integer fret){
        Integer openNote = Constants.stringMap.get(string.intValue());
        int note = openNote.intValue() + fret.intValue();
        if (note >= 12) {
            note = note - 12;
        }
        Sound sound = SoundFactory.getSound(Constants.notes.get(note));
        return "String: " + string + " Fret: "+ fret + " Sound: " + sound.Play();
    }
}
```

_Constants.java_

```java
package flyweight.server;

import java.util.HashMap;

public class Constants {
    static HashMap<Integer, Integer> stringMap = new HashMap<Integer, Integer>();
    static HashMap<Integer, String> notes = new HashMap<Integer, String>();
    static void setup() {
        stringMap.put(1, 4);
        stringMap.put(2, 11);
        stringMap.put(3, 7);
        stringMap.put(4, 2);
        stringMap.put(5, 9);
        stringMap.put(6, 4);

        notes.put(0, "C");
        notes.put(1, "C#");
        notes.put(2, "D");
        notes.put(3, "D#");
        notes.put(4, "E");
        notes.put(5, "F");
        notes.put(6, "F#");
        notes.put(7, "G");
        notes.put(8, "G#");
        notes.put(9, "A");
        notes.put(10, "A#");
        notes.put(11, "B");

    }
}

```

_Sound.java_

```java
package flyweight.server;

public interface Sound {
    String Play();
}

```

_SoundImpl.java_

```java
package flyweight.server;

public class SoundImpl implements Sound {
    private String note;

    public SoundImpl(String note) {
        this.note = note;
    }

    public String Play() {
        return this.note + "- note";
    }

    public String getNote() {
        return this.note;
    }
}

```

_Application.java_

```java
package flyweight.server;

import io.micronaut.runtime.Micronaut;

public class Application {

    public static void main(String[] args) {
        Constants.setup();
        Micronaut.run(Application.class, args);
    }
}

```

# 実行結果

```powershell
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing C:\Users\tsushi\Code\java\spike\micronaut\flyweight-server\target\flyweight-server-0.1.jar with C:\Users\tsushi\Code\java\spike\micronaut\flyweight-server\target\flyweight-server-0.1-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  7.444 s
[INFO] Finished at: 2020-10-26T14:16:48-07:00
[INFO] ------------------------------------------------------------------------
PS C:\Users\tsushi\Code\java\spike\micronaut\flyweight-server> java -jar .\target\flyweight-server-0.1.jar
←[36m14:17:24.022←[0;39m ←[1;30m[main]←[0;39m ←[34mINFO ←[0;39m ←[35mio.micronaut.runtime.Micronaut←[0;39m - Startup completed in 1154ms. Server Running: http://localhost:8080
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/8b5eaa03-c061-77b3-f8b5-4593dd3807c6.png)

今日は軽く `Flyweight` パターンと、`ConcurrentHashMap` を学べたので良しとしましょう。

# Resource 
* [Java Concurrent Collections - 06 - ConcurrenthashMap with example](https://www.bing.com/videos/search?view=detail&mid=79C5B471741D7E7BDA1679C5B471741D7E7BDA16&q=java+concurrent+hashmap&shtp=GetUrl&shid=5f02dcde-b239-4aed-b49e-b2093fc255b6&shtk=SmF2YSBDb25jdXJyZW50IENvbGxlY3Rpb25zIC0gMDYgLSBDb25jdXJyZW50SGFzaE1hcCB3aXRoIGV4YW1wbGU%3D&shdk=SmF2YSBDb25jdXJyZW50IENvbGxlY3Rpb25zLiBJbXBsZW1lbnRpbmcgYW4gZXhhbXBsZSB3aXRoIENvbmN1cnJlbnRIYXNoTWFwLiBDb21wbGV0ZSBDb3Vyc2UgMjAwKyBWaWRlb3MsIDI0IEhvdXJzIC0gSkFWQSBQUk9HUkFNTUlORyBmb3IgQ29tcGxldGUgQmVnaW5uZXJzIGluIDI1MCBTdGVwcyAtIGh0dHBzOi8vbGlua3MuaW4yOG1pbnV0ZXMuY29tL2luMjhtaW51dGVzLWphdmEgRm9sbG93IFJhbmdhIG9uIExpbmtlZEluIC0gaHR0cHM6Ly93d3cubGlua2VkaW4uY29tL3Bvc3RzL3Jhbmdha2FyYW5hbV90aGFuay15b3Uta2VlcC1sZWFybmluZy1ldmVyeS1kYXktb3VyLWFjdGl2aXR5IC4uLg%3D%3D&shhk=1fInG0BpLUB9HJgq7GeDEImuhHB7KRa20yXlYDLLZ8Q%3D&form=VDSHOT&shth=OSH.AVMVCb6LVt9mmNbKxQ05eA)

* [Class ConcurrentHashMap<K,V>](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html)