「このプロパティはstatic指定しましょう。ただし(オブジェクト間で共有するものを)なんでもstatic指定すればいいわけではありません。」
今回は新人の私が現場でもらった上記の指摘の意味について備忘として残したいと思います。一旦温かい目で見守っていただけると幸いです。
表題の通り、今回はstatic指定する際に留意するべきことについて説明しますが、その前に簡単に「そもそもstatic指定するメリット」を整理しておきましょう。
static指定のメリット
最もわかりやすいものはやはり「メモリ効率とリソースの共有」でしょう。
staticな変数は、アプリ起動時やクラスが最初にロードされた時点でメモリに確保され、プログラム終了までその状態を保持します。このため、オブジェクトを何回生成しても、そのデータ領域は常に1つだけであり、複数のインスタンス間でデータを共有できます。ゆえにインスタンスごとに同じデータを重複して持つ必要がなくなり、メモリ効率が向上します。
まあ私はそんなことも忘れて無我夢中でコーディングをしてしまい、オブジェクト間で共有すべきプロパティにstaticをつけていませんでした...
スレッドとは
そうしていると当たり前のように冒頭の指摘が返ってくるわけですが、皆さんは後半の指摘の意味がわかりますでしょうか?
結論から言うと「スレッドセーフ」という概念に留意する必要があります。
その前に私はまずスレッドという概念から理解する必要がありました。
まず実行中のプログラムは「プロセス」と呼ばれ、プロセス内の最小の実行単位が「スレッド」と呼ばれます。
そして複数のスレッドから同時にアクセスされたり操作されたりしても、そのプログラムやデータ構造(オブジェクト、メソッドなど)が常に正しく動作し、期待通りの結果を返すことが保証されている状態のことを「スレッドセーフ」といいます。
static指定する際に気を付けること
もうある程度話のオチが見えていますが、つまるところあるプロパティをstatic指定することは複数のインスタンスから共通化したメモリ領域にアクセスされることを意味します。
このときスレッドセーフでないと、同時にそのプロパティの変更が行われデータの整合性が崩れてしまう可能性があります。
例
最後にその例を一つ提示して終わりましょう。仮定として下記のような指定をしてしまったとします。
public static List<String> list = new ArrayList<>();
このときスレッド1がlist.add("データA") を実行。スレッド2がlist.add("データB") を実行。
みたいな状況が起きてしまうと、先に説明したサイズ不整合やデータ破損といった競合状態の不具合が発生します。
ちなみ余談ですがこれfinal指定すればいいんだっけ?と私は直感的に思ってしまいましたが、もちろんそれは解決策になりえません。
この場合listという変数はArrayListのインスタンスへの参照を持っているだけで、それをfinal指定することは参照を固定することを意味します。
つまりlist = new ArrayList<>()みたいな操作を拒否することはできますが、インスタンスそのものの変更はできてしまうためです。