Spring Controller単体例とスレッドの安全はそれらのことです.

8078 ワード

目次
  • 単例(singleton)作用域
  • プロトタイプ機能領域
  • 以上のHTTP要求は、Springコントローラの内部でシリアルまたはパラレルに実行されますか?
  • 単例モードを実現し、大量の同時要求をシミュレーションし、スレッドセキュリティ
  • を検証する.
  • 付録:Spring Bean作用領域
  • 単例(singleton)スコープ
    各@RertControllerまたは@Controllerを追加するコントローラは、デフォルトは単一の例であり、これもSpring Bernのデフォルトのスコープである.
    以下のコード例はBuiilding a RESTful Web Serviceを参照してください.このチュートリアルはSpring Bootに基づくウェブプロジェクトを構築しています.ソースコードはspring-gggggggguides/gs-serviceを参照してもいいです.
    Greeeting Controller.javaコードは以下の通りです.
    package com.example.controller;
    
    import java.util.concurrent.atomic.AtomicLong;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class GreetingController {
    
        private static final String template = "Hello, %s!";
        private final AtomicLong counter = new AtomicLong();
    
        @GetMapping("/greeting")
        public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
            Greeting greet =  new Greeting(counter.incrementAndGet(), String.format(template, name));
            System.out.println("id=" + greet.getId() + ", instance=" + this);
            return greet;
        }
    }
    
    HTTP基準ツールwrkを用いて大量のHTTP要求を生成した.端末に以下のコマンドを入力してテストします.
    wrk -t12 -c400 -d10s http://127.0.0.1:8080/greeting
    
    サービスの標準出力では、類似のログが見られます.
    id=162440, instance=com.example.controller.GreetingController@368b1b03
    id=162439, instance=com.example.controller.GreetingController@368b1b03
    id=162438, instance=com.example.controller.GreetingController@368b1b03
    id=162441, instance=com.example.controller.GreetingController@368b1b03
    id=162442, instance=com.example.controller.GreetingController@368b1b03
    id=162443, instance=com.example.controller.GreetingController@368b1b03
    id=162444, instance=com.example.controller.GreetingController@368b1b03
    id=162445, instance=com.example.controller.GreetingController@368b1b03
    id=162446, instance=com.example.controller.GreetingController@368b1b03
    
    ログのすべてのGreeetingControllerのインスタンスのアドレスは同じであり、複数の要求が同一のGretingControllerのインスタンスを処理し、そのAtomicLongタイプのcounterフィールドは予想通りにコール毎にインクリメントされている.
    プロトタイプのスコープ
    もし私達が@RestControllerの注釈の上に@Scope(「prototype」)の注釈を追加すれば、beanの作用領域を原型の作用領域に変え、その他の内容はそのまま維持されます.
    ...
    
    @Scope("prototype")
    @RestController
    public class GreetingController {
        ...
    }
    
    サービス端末の標準出力ログは以下の通りで、プロトタイプのスコープに変更した後、要求ごとに新しいbeanを作成すると説明していますので、戻るidは常に1で、beanのインスタンスアドレスも違います.
    id=1, instance=com.example.controller.GreetingController@2437b9b6
    id=1, instance=com.example.controller.GreetingController@c35e3b8
    id=1, instance=com.example.controller.GreetingController@6ea455db
    id=1, instance=com.example.controller.GreetingController@3fa9d3a4
    id=1, instance=com.example.controller.GreetingController@3cb58b3
    
    複数のHTTP要求は、Springコントローラ内部でシリアルまたはパラレルに実行されますか?
    greeting()方法でスリープ時間を増やすと、各http要求がコントローラ内の方法をシリアルで起動するかどうかを確認します.
    @RestController
    public class GreetingController {
    
        private static final String template = "Hello, %s!";
        private final AtomicLong counter = new AtomicLong();
    
        @GetMapping("/greeting")
        public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) throws InterruptedException {
            Thread.sleep(1000); //   1s
            Greeting greet =  new Greeting(counter.incrementAndGet(), String.format(template, name));
            System.out.println("id=" + greet.getId() + ", instance=" + this);
            return greet;
        }
    }
    
    やはりwrkを用いて大量の要求を作成することは、サービス端末の方法で休止しても1秒で、各要求の平均遅延は1.18 sに達したが、毎秒処理できる要求は166件に達しており、HTTP要求はシリアルではなくSpring MVC内部でコントローラを同時起動する方法であることを証明している.
    wrk -t12 -c400 -d10s http://127.0.0.1:8080/greeting
    
    Running 10s test @ http://127.0.0.1:8080/greeting
      12 threads and 400 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     1.18s   296.41ms   1.89s    85.22%
        Req/Sec    37.85     38.04   153.00     80.00%
      1664 requests in 10.02s, 262.17KB read
      Socket errors: connect 155, read 234, write 0, timeout 0
    Requests/sec:    166.08
    Transfer/sec:     26.17KB
    
    単一の例モードを実現し、大量の同時要求をシミュレーションし、スレッドの安全を検証する.
    単例類の定義:Singleton.java
    package com.demo.designpattern;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Singleton {
    
        private volatile static Singleton singleton;
        private int counter = 0;
        private AtomicInteger atomicInteger = new AtomicInteger(0);
    
        private Singleton() {
        }
    
        public static Singleton getSingleton() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    
        public int getUnsafeNext() {
            return ++counter;
        }
    
        public int getUnsafeCounter() {
            return counter;
        }
    
        public int getSafeNext() {
            return atomicInteger.incrementAndGet();
        }
    
        public int getSafeCounter() {
            return atomicInteger.get();
        }
    
    }
    
    単一の例の種類をテストして、大量の要求を作成し、同時に呼び出します.Singleton Test.java.
    package com.demo.designpattern;
    
    import java.util.*;
    import java.util.concurrent.*;
    
    public class SingletonTest {
    
        public static void main(String[] args) {
            //                 Callback  
            Callable unsafeCallableTask = () -> Singleton.getSingleton().getUnsafeNext();
            runTask(unsafeCallableTask);
            // unsafe counter may less than 1000, i.e. 984
            System.out.println("current counter = " + Singleton.getSingleton().getUnsafeCounter());
    
            //                Callback  (  AtomicInteger)
            Callable safeCallableTask = () -> Singleton.getSingleton().getSafeNext();
            runTask(safeCallableTask);
            // safe counter should be 1000
            System.out.println("current counter = " + Singleton.getSingleton().getSafeCounter());
    
        }
    
        public static void runTask(Callable callableTask) {
            int cores = Runtime.getRuntime().availableProcessors();
            ExecutorService threadPool = Executors.newFixedThreadPool(cores);
            List> callableTasks = new ArrayList<>();
            for (int i = 0; i < 1000; i++) {
                callableTasks.add(callableTask);
            }
            Map frequency = new HashMap<>();
            try {
                List> futures = threadPool.invokeAll(callableTasks);
                for (Future future : futures) {
                    frequency.put(future.get(), frequency.getOrDefault(future.get(), 0) + 1);
                    //System.out.printf("counter=%s
    ", future.get()); } } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } threadPool.shutdown(); } }
    付録:Spring Beanの役割領域
    範囲
    説明
    singleton(単例)
    (デフォルト値)各Spring IoC容器の単一bean定義範囲を単一のオブジェクト例に限定する.言い換えれば、beanを定義し、そのスコープを一例とすると、Spring IoC容器は、beanによって定義されたオブジェクトのためのインスタンスを作成します.この単一の例は単一の例beansのキャッシュに格納され、beanと命名されたすべての後続の要求および参照はキャッシュの対象に戻る.
    プロトタイプ(原型)
    単一のbeanによって定義されたスコープは、任意の数のオブジェクトの例に限定される.ビーンプロトタイプスコープは、特定のビーンに要求するたびに、新しいビーンインスタンスを作成する.つまり、ビーンを別のビームに注入するか、または、コンテナ上のgetBean()方法を呼び出して要求することができます.通常、プロトタイプスコープは全ての状態Beanに使用され、単一の作用領域は無状態Beanに使用されるべきである.
    request
    単一のbeanで定義される範囲を単一のHTTP要求のライフサイクルに限定する.つまり、各HTTP要求には、単一のbean定義後に作成されたbeanの例がある.web-awareのSpring Application Contectコンテキストだけで有効です.
    セッション
    単一beanで定義された範囲をHTTP Sessionのライフサイクルに限定します.webベースのSpring Apple Controtextコンテキストだけで有効です.
    appication
    単一のbeanで定義された範囲をServlet Contectのライフサイクルに限定します.webベースのSpring Apple Controtextコンテキストだけで有効です.
    websocket
    単一のbeanで定義されたスコープはWebsocketのライフサイクルに限定されます.webベースのSpring Apple Controtextコンテキストだけで有効です.