0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【並列デザインパターン連載】Immutableパターン

Last updated at Posted at 2021-10-30

↓前回記事

非同期デザインパターンシリーズの第2弾です。前回は「Single Threaded Executionパターン」を紹介しました。

##参考図書
https://www.amazon.co.jp/%E5%A2%97%E8%A3%9C%E6%94%B9%E8%A8%82%E7%89%88-Java%E8%A8%80%E8%AA%9E%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E5%85%A5%E9%96%80-%E3%83%9E%E3%83%AB%E3%83%81%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E7%B7%A8-%E7%B5%90%E5%9F%8E-%E6%B5%A9/dp/4797331623

この本の第2章「Immutableパターン」を基にしています。

##Immutableパターンとは?
前回記事の「Single Threaded Executionパターン」では、複数スレッドから使われた際に想定の動作をしないクラス/メソッドに対して、排他制御を行うことで想定の動きを保証するという考え方をしました。

今回はそもそも排他制御を行わなくてもよいようにするというImmutableパターンの思想でクラス設計を行います。

"Immutable"とは"変化しない"の意味で、クラスがマルチスレッドで稼働しても変化しない状態を表します。

##Immutableなクラスである条件
こうなっていれば必ずImmutableだ、という条件はないと思っています。が、最低限チェックが必要なのは下記でしょうか。

  • そのクラスにクラス変数を変更するメソッド(setterメソッド)はないこと
  • クラスが継承された際に、クラス変数を変更するメソッドが追加される可能性がないこと
  • クラス変数が参照するインスタンスの状態が変更される可能性がないこと

個人的には、これらをきっちり担保するのは難しく、クラスが真にImmutableになるよう設計できるのはレアケースなんじゃないかと思ってます。

##具体例
####クラス設計

名前 役割
Person 人を表すクラス
Main 動作テストクラス
PrintPersonThread Personのインスタンス情報を表示するスレッドを表すクラス

####実装
※こちら↓からDL可能です。
※筆者が作ったものではありません
https://www.hyuki.com/dp/dp2.html#download

Main.java
public class Main {
    public static void main(String[] args) {
        Person alice = new Person("Alice", "Alaska");
        new PrintPersonThread(alice).start();
        new PrintPersonThread(alice).start();
        new PrintPersonThread(alice).start();
    }
}
PrintPersonThread.java
public class PrintPersonThread extends Thread {
    private Person person;
    public PrintPersonThread(Person person) {
        this.person = person;
    }
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " prints " + person);
        }
    }
}
Person.java
public final class Person {
    private final String name;
    private final String address;
    public Person(String name, String address) {
        this.name = name;
        this.address = address;
    }
    public String getName() {
        return name;
    }
    public String getAddress() {
        return address;
    }
    public String toString() {
        return "[ Person: name = " + name + ", address = " + address + " ]";
    }
}

特にPersonクラスの作りについて、下記の特徴があります。

  • finalクラスとなっており、継承を禁止している
  • クラス変数(name,address)がprivateとなっており、Personクラスからのみ使用可能
  • クラス変数(name,address)がfinalとなっており、setterメソッドがない。これにより、コンストラクタ生成時以外で代入が不可能

####動作確認

※一部抜粋

Thread-1 prints [ Person: name = Alice, address = Alaska ]
Thread-1 prints [ Person: name = Alice, address = Alaska ]
Thread-0 prints [ Person: name = Alice, address = Alaska ]
Thread-0 prints [ Person: name = Alice, address = Alaska ]
Thread-0 prints [ Person: name = Alice, address = Alaska ]
Thread-0 prints [ Person: name = Alice, address = Alaska ]
Thread-2 prints [ Person: name = Alice, address = Alaska ]
Thread-2 prints [ Person: name = Alice, address = Alaska ]
Thread-2 prints [ Person: name = Alice, address = Alaska ]
Thread-2 prints [ Person: name = Alice, address = Alaska ]
Thread-2 prints [ Person: name = Alice, address = Alaska ]
Thread-0 prints [ Person: name = Alice, address = Alaska ]

複数スレッドからtoStringメソッドが呼び出されていますが、期待通りにPersonの情報を出力し続けています。
toStringをsyncronizedメソッドにしなくても、期待通りに動いています。
これはPersonクラスが一度作られた後にフィールドの値を変更できない、つまりImmutableであるからです。

ImmutableっぽいけどImmutableじゃないパターン

setterメソッドもないし、クラス変数もfinalにしてコンストラクタからしか変更できないようにしたけど..

UserInfo.java
public final class UserInfo {
  private final StringBuffer info;
  public UserInfo(String name, String address) {
    this.info = new StringBuffer("****");
  }  
  public StringBuffer getInfo() {
    return info;
  }  
  public String toString() {
    return "[ UserInfo: " + info * " ]";
  }
}

結論からいうと、このクラスはImmutableではありません。下記のように、クラス変数がfinalであっても、変数の参照先インスタンスの状態がImmutableでなければ、複数スレッドから変更される可能性があります。

UserInfo.java
//変数infoの参照先インスタンスを取得
StringBuffer buffer = getInfo(); 
//文字順を逆にする
buffer.reverse();

PersonクラスがImmutableであったのは、java.lang.StringクラスがImmutableなクラスだったからです。これに対しStringBuffer,StringBuilderなどのクラスは自らの状態を変えるメソッドを持っているため、可変です。

上記のUserInfoクラスはは冒頭で述べたチェック項目

  • クラス変数が参照するインスタンスの状態が変更される可能性がないこと

に違反しているため、Immutableではなくなっていたのでした。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?