はじめに
はじめまして、学生エンジニアの @huyunoki です。
今回痛感したのは「マルチスレッドはメリットもすごいけど、デメリットも同じくらい(いや、それ以上に)エグい」ということです。
本日は、自分が開発したアプリでJavaのマルチスレッドを作る Thread クラスが引き起こした不具合についての備忘録をこれから設計に挑戦する皆さんに共有していきます。
1. なぜ僕たちはマルチスレッドに惹かれるのか
まず、マルチスレッドがどんなものか軽くおさらいしましょう。
大塚商会のサイトではこう定義されています。
- アプリケーションのプロセス(タスク)を複数のスレッドに分けて並行処理する方式
大塚商会 IT用語辞典
Oracleのドキュメントを読んでも、良いことばかり書いてあるんですよね。
- とにかく速い!: 並列で動くから処理時間がギュッと短くなる。
- サクサク動く: 重い処理を裏側に回せるから、画面が固まらない(砂時計にならない)。
- コスパが良い: PCのパワーを無駄なく使い切れる。
- コードが綺麗?: 1つ1つの処理をバラバラに作れるから、設計がシンプルになる(らしい)。
Oracle社 ドキュメント
これだけ見ると「マルチスレッド最強じゃん!」って思いますよね。僕もそう思っていました。最近までは……
2. 1件なら動くのに30件で壊れる?実録「スレッドの恐怖」
今回の不具合は、自作した「自動メール送信機能」に潜んでいました。
当初の設計(ここが罠だった)
本来は「メールの内容をセットしてから送信する」という単純な流れ。
でも、速度を優先したのか「内容をセットする処理」と「送信する処理」が別々のスレッドで動く設計になっていたんです。
さらにまずかったのが、送信内容をクラスのインスタンス変数(みんなで共有する場所)に置いていたことでした。
なぜ今まで動いていたのか?
設計された当時は、メールを送るトリガーは「1回に1件」しか発生しませんでした。
スレッドが分かれていても他の処理がないので、共有変数が書き換えられることもなく、平和に動いていたわけです。
拡張した瞬間に牙を剥いた
ところが最近「最大30件まで一括で登録できる」というアプデが入れました。
その瞬間、今回の題材となっているマルチスレッドの危険性が牙を向きました。
1件目の内容をセットしている隙に、2件目のスレッドがやってきて共有変数を上書き。
送信スレッドはパニックになり「Aさんの宛先に、Bさんの本文が届く」という、最悪なバラバラ事件が発生してしまったのです……。
3. 結局、何が良くて何がダメなの?(まとめ)
設計をする時に絶対に忘れてはいけないポイントを表にしました。
| 項目 | メリット (イイところ) | デメリット (コワいところ) |
|---|---|---|
| 速さ | 複数を同時に動かせるので時短になる | スレッドの切り替え自体に意外とパワーを使う |
| リソース | メモリを効率よくシェアできる | シェアしてる場所を奪い合ってデータが壊れる |
| 使い心地 | 裏で重い処理をしても画面が止まらない | 不具合がたまにしか起きず、デバッグが地獄 |
| コード | 処理を独立させて書ける | 「データの安全」を守るためのコードが超複雑になる |
4. 設計者の先輩にお願い:安易に「Thread」を使わないで!
今回のバグ、直すのはめちゃくちゃ大変でした。でも一番の原因は実装ミスというより「スレッド間でデータを共有する怖さ」への認識不足だった気がします。
これから設計をする皆さんに、僕からのお願いです!
- そのデータ、後から変えられないようにできる?: スレッドに渡すデータは一度決めたら絶対変わらない(Immutability)形にしてください。
- 数が100倍になっても大丈夫?: 「今は1件だからOK」は数年後の後輩を殺す言葉です。
- デバッグする人の顔を想像して!: マルチスレッドのバグは本番の忙しい時にしか起きません。再現できないバグは絶対に押し付けないでください(切実)。
マルチスレッドは言わば「開発の諸刃」です。
「なんとなく速そう」だけで装備するにはリスクが高すぎます。
その設計、本当に「普通の処理 + 賢いアルゴリズム」じゃダメですか?
設計図に Thread と書き込む前に、どうか一度だけ踏みとどまって考えてみてください。