66163


サーバー上のアプリケーション内に、あるクラスの静的変数の値を更新するAPIを実装したとします。  
このAPIに不特定多数のユーザが接続した際の挙動に関して質問です。

例えば以下の2つのクラスを考えます(Javaで書いてます)。

[code]

    public class SampleClass {
        public static int x;
        public static void sampleMethod() {
            x += 1;
        }
    }
    
[/code]

[code]

    public class SampleClass2 {
        public int sampleMethod2() {
            SampleClass.sampleMethod();
            return SampleClass.x;
        }
    }
    
[/code]

API内ではこの`sampleMethod2()`を1回呼び出し、その戻り値がAPIのレスポンスとして手元に渡ってくる想定です。  
このAPIをA, Bという別のユーザが、各々に呼びだしたときにそれぞれが得られた値の組み合わせを(A, B)と表現するとします。  
**質問内容は、（2,2）の組み合わせが得られる可能性というのはあるのかどうか、ということです。**

私の理解は以下のとおりです。  
1\. AがAPIを呼びだした後に、アプリケーションが終了する前にBがAPIを呼びだした場合：(1,2)  
2\. AがAPIを呼びだした後にアプリケーションが終了、その後Bがアプリケーション起動&APIを呼びだした場合：(1,1)  
3\. A,
Bがほぼ同時でAPIを呼び出し、それぞれの処理が非同期的に進行し、Aの`SampleClass.sampleMethod()`の次にBの`SampleClass.sampleMethod()`が実行された場合：(2,2)  
以上の理解は正しいでしょうか。

更新対象が静的変数ではなく、例えばDBの同じレコードだった場合、最初の更新時にレコードがロックされる為３のような状態は起きないと思っていますが、静的変数だった場合はどうなるのかと思い質問した次第です。

また、もし３の状態がありえない場合、それはAの処理が完了した後にBの処理が確実に実行されるような機構が存在すると考えられますが、それにはどのようなものがあるのでしょうか。キーワードだけでも教えていただけると助かります。  
**※Javaに限らずお聞きしたいです。**

以上、よろしくお願いします。


CC BY-SA 4.0
2020-04-30T14:17:27.787
66158
2020-05-01T04:30:25.933
2020-04-30T15:19:32.913
39901
39901
question
api
静的変数(クラス変数)の値を、API経由で不特定多数が同時に更新した際の挙動について


普通に2,2あり得ますね。だって、インクリメントと値取得を別にやっているんですから。  
A⇒インクリメント、B⇒インクリメント A返却、B返却は十分にあり得ます。（メソッドと参照が一緒になっていても、同じことはおこります。）  
Javaであれば値更新から返却までを1つのメソッドに閉じ込め、synchronized 指定してやればいいのでは？と考えます。

あ、そもそもこういう風に大域変数でずーーーっと持ち続けることの是非は別途検討しておくべきですよね。事故の元ですし。。ステートレスに作ったほうが取り回しやすいですよね。

普通に考えて、プロセスの中に値を閉じ込めておくと、受け手の水平スケーラビリティを確保できなくなるので、アクセス数にあわせて水平にスケールするのが常套手段なAPIの世界で、この実装は致命的かなぁ～と考えたりします。（スケールしたときにプロセスごとに値が変わりますから。。）

仮に「ここだけはダイジョブ。影響ないから」とか考えていたとして、そういうのが1つでもあると、結局スケールするときに、全体調査が走ったりするので、面倒なだけですよね。と


CC BY-SA 4.0
2020-04-30T16:10:52.340
66161
2020-04-30T16:37:52.177
2020-04-30T16:37:52.177
10174
10174
66158
answer


質問文から読み取れない条件があるので補足しならがら回答してみます。

* * *

`SampleClass2#sampleMethod2()` を2回呼び出した結果 `SampleClass.x` の値が確実に `2`
になるのは、それら2つの呼び出しが同一スレッドで行われた場合のみです。

この条件を満たさない状況の具体例としては次のようなものがあります:

  * 別々のクラスローダでロードしたクラスで実行した場合: 常に `1`。
  * 同一クラスローダでロードしたクラスだが異なるスレッドで実行した場合: `2` になる可能性もあるが `1` になる可能性もある。

質問文からはAPI(これはWeb
APIという理解で良いでしょうか)を呼び出した場合この処理が実行されると書かれていますが、その際に新しくクラスロードが行われるのか(≒別のプロセスで実行されるのか)そうではなく同じクラスが使いまわされるのか(≒同一プロセスで実行されるのか)が曖昧です。

  * Tomcatのようなアプリケーションサーバ上で実行する場合: 常に同じクラスローダが用いられる
  * いわゆるサーバレスアーキテクチャ上で実行する場合: 常に異なるクラスローダが用いられる

質問文では「アプリケーションが終了」という状態が発生しているのでサーバレスアーキテクチャ的なものを想定されているのかと考えましたが、その場合、常に異なるクラスローダが用いられますので常に結果は
`1` です。

(要するに、「アプリケーションが終了」というのが何を指しているのかによって回答が変わってきます。)

* * *

「アプリケーションが終了」について一旦無視して可能性を考えると、1., 2., 3. いずれも:

  * 異なるクラスローダを用いた場合: `(1,1)`
  * 同一クラスローダでロードしたクラスを用い同一スレッドで処理した場合: `(1,2)`
  * 同一クラスローダでロードしたクラスを用い異なるスレッドで処理した場合: `(1,1)`, `(1,2)`, `(2,1)`, `(2,2)`

です。  
つまり、

> （2,2）の組み合わせが得られる可能性というのはあるのかどうか

の回答としては、  
「同一クラスローダでロードしたクラスを用い異なるスレッドで処理した場合にはあり得るがそうでないのなら有り得ない(そしてそれは質問の解釈次第だが3.に限らず1.も2.も同じである)」  
です。

* * *

>
> もし３の状態がありえない場合、それはAの処理が完了した後にBの処理が確実に実行されるような機構が存在すると考えられますが、それにはどのようなものがあるのでしょうか。

Javaについてであれば  
[`java.util.concurrent` >
メモリー整合性特性](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/package-
summary.html#package-summary.S60kC), [The Java™ Tutorials >
Concurrency](https://docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.html)などに言及があります。

* * *

(コメントを受けて追記)  
1., 2., 3.,
の並びから「アプリケーションの終了」という状態について何か意図があるのかと考えていましたが、上で挙げた「Tomcatの終了」のような意味合いで良さそうですね。

そうすると、アプリケーションが途中で終了していない1.,3.,については

>   1. AがAPIを呼びだした後に、アプリケーションが終了する前にBがAPIを呼びだした場合：(1,2)
>

  * `(1,1)`, `(1,2)`

>   3. A,
> Bがほぼ同時でAPIを呼び出し、それぞれの処理が非同期的に進行し、AのSampleClass.sampleMethod()の次にBのSampleClass.sampleMethod()が実行された場合：(2,2)
>

  * `(1,1)`, `(1,2)`, `(2,1)`, `(2,2)`

があり得る組み合わせです。  
要は、一方の処理結果が他方に反映されていない可能性もある、ということです。

前述リンク先

> あるスレッドによる書込みの結果が別のスレッドによる読取りで認識されることが保証されるのは、その書込み操作が読取り操作の前に発生(happens-
> before)した場合だけです。
> synchronized構文とvolatile構文のほか、Thread.start()メソッドとThread.join()メソッドがhappens-
> before関係を形成できます。

にある通り、このコードはhappens-
before関係が形成されていない(`synchronized`や`volatile`が利用されていない)のが理由です。

残りの、アプリケーションが終了する2.については

> 2\. AがAPIを呼びだした後にアプリケーションが終了、その後Bがアプリケーション起動&APIを呼びだした場合：(1,1)

書かれている通りです。

* * *

「クラスローダ」という用語を登場させた理由について。

Javaの`static`変数はC言語の`static`変数とは異なり、クラスに所属する領域です。  
つまり`static`変数の寿命は(アプリケーションの寿命ではなく)クラスの寿命と一致します。  
そしてクラスの寿命を司るのがクラスローダです。

  * アプリケーション(JVMプロセス)を終了させなくてもクラスを消せば領域は消える
  * 複数のクラスローダを用いてそれぞれでクラスをロードするとそれらのクラスの`static`変数は別物として扱われる(共有されない)

Tomcatのようなアプリケーションサーバではユーザアプリケーション(war)を複数動作させることができますし、Tomcat起動中に新しいユーザアプリケーションをデプロイしたり逆にアンデプロイしたりすることもできます。

質問文中の「アプリケーションの終了」が、Tomcatの終了を指すものかユーザアプリケーションの終了(アンデプロイ)を指すものかが不明確であったため、「アプリケーションの終了」に依存しない回答となるよう登場させました。

今回の質問に対しては無関係であることがわかりましたので気にしなくて良いです。  
(アプリケーションとクラスの寿命は同じであり、C言語と同じようなイメージで良い)

クラスローダとは何か/ロードとは何か、については今回の質問の本質とは外れるので別の質問を立ててください。


CC BY-SA 4.0
2020-04-30T20:16:56.587
66163
2020-05-01T04:30:25.933
2020-05-01T04:30:25.933
2808
2808
66158
answer