簡體   English   中英

在Spring Boot應用程序中具有線程ID的@Scope(“ singleton”)

[英]@Scope(“singleton”) with thread Id in Spring Boot application

我正在使用由TaskExecutor執行的線程在Spring Boot項目中工作。 據我所知, @Scope("singleton")表示如果我希望Spring返回相同的bean實例,那么如果我在帶有@Component注釋的線程上聲明它,Spring將僅返回該線程。 當我嘗試通過TaskExecutor多次執行該線程時,我認為它每次應返回相同的線程ID,但似乎返回不同的結果。 有人可以幫我解釋一下嗎?

@Component
@Scope("singleton")
public class MyThread implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyThread.class);

    @Override
    public void run() {
        LOGGER.info("Called from thread + " + Thread.currentThread().getId());
    }
}

我有執行上述線程的服務:

@Service
public class AsynchronousService {

    @Autowired
    private TaskExecutor taskExecutor;

    @Autowired
    private ApplicationContext applicationContext;

    public void executeAsynchronously() {
        MyThread myThread = applicationContext.getBean(MyThread.class);
        taskExecutor.execute(myThread);
    }

我的配置文件:

@Configuration
@EnableAsync
public class ThreadConfig {

    @Bean
    @Primary
    public TaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(4);
        executor.setThreadNamePrefix("default_task_executor_thread");
        executor.initialize();
        return executor;
    }

然后我有一個控制器:

@RestController
public class HelloController {

    @Autowired
    private AsynchronousService asynchronousService;

    @RequestMapping("/runTask")
    public String executeAsync() {
        asynchronousService.executeAsynchronously();
        return "OK";
    }

結果如下:

2019-06-05 11:48:21.019  INFO 4056 --- : Called from thread + 97
2019-06-05 11:48:22.932  INFO 4056 --- : Called from thread + 101
2019-06-05 11:48:23.119  INFO 4056 --- : Called from thread + 65
2019-06-05 11:48:23.372  INFO 4056 --- : Called from thread + 93
2019-06-05 11:48:23.531  INFO 4056 --- : Called from thread + 97
2019-06-05 11:48:23.799  INFO 4056 --- : Called from thread + 101
2019-06-05 11:48:23.961  INFO 4056 --- : Called from thread + 65

我認為您可以在問題中更好地解釋單例的概念:

有一個應用程序上下文-如果需要,可以顯示所有spring bean的全局映射。

現在,Singleton意味着每次您請求bean時(就像您直接調用applicationContext.getBean或者如果spring本身是為了注入而這樣做的),都將返回該對象的相同實例。 它與不同的線程無關。

換句話說,如果您運行多個線程並要求應用程序上下文獲取單例bean,它將始終返回相同的實例。

對於不同的原型,總是會創建一個新實例。

因此,如果此實現:

public class MyThread implements Runnable {

private static final Logger LOGGER = LoggerFactory.getLogger(MyThread.class);

   @Override
   public void run() {
      LOGGER.info("Called from thread + " + Thread.currentThread().getId());
   }
}

嘗試執行類似的檢查操作,您將了解我在說什么:

 public class MyThread2 implements Runnable {
    private Object random = //  createRandomLong // or UUID or whatever
    private static final Logger LOGGER = ...
    public void run() {
        LOGGER.info("Called the bean: " + random);      
    }        
 }

現在從不同的線程運行它(您將看到它的相同實例)

現在,一般而言,Spring可以完美地在多線程環境中工作,例如,可以由不同的客戶端同時調用控制器(因此,它具有不同的線程,因為它是每個請求線程的模型)。

它與多線程無關,而與注入有關。

希望來自@BoristheSpider的評論消除了您對Thread和Runnable的懷疑。
關於單例, 此答案將幫助您了解更多。
我將嘗試回答OP的聲明

當我嘗試通過TaskExecutor多次執行該線程時,我認為它每次應返回相同的線程ID,但似乎返回不同的結果。 有人可以幫我解釋一下嗎?

就像我們使用不同的助手來完成某些工作一樣,這里的助手是線程,而工作就是您的業務邏輯(在MyThread中)。
假設我們只有一名助手來完成我們的任務,這將花費大約10秒鍾的時間,而我們需要做3次此工作。
但是由於我們只有1名工作人員,因此完成任務至少需要10 + 10 + 10 = 30s。
在下面的測試類中,我增加了30s的睡眠時間,以便所有子線程可以在父線程完成其執行之前完成其工作。
OP中的MyThread.java
添加了更多日志和睡眠。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("singleton")
public class MyThread implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyThread.class);

    @Override
    public void run() {
        LOGGER.info("Called from thread + " + Thread.currentThread().getId());
        LOGGER.info("Thread info+ " + Thread.currentThread().getName());
        LOGGER.info("Thread info+ " + Thread.currentThread().getThreadGroup());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ThreadConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@EnableAsync
@Configuration
public class ThreadConfig {
    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(1);
        executor.setThreadNamePrefix("default_task_executor_thread");
        executor.initialize();
        return executor;
    }
}

測試班

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class DemoApplicationTests {
    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    Executor threadPoolTaskExecutor;

    @Test
    public void test() throws InterruptedException {
        MyThread myThread = applicationContext.getBean(MyThread.class);
        if (threadPoolTaskExecutor != null) {
            threadPoolTaskExecutor.execute(myThread);
            threadPoolTaskExecutor.execute(myThread);
            threadPoolTaskExecutor.execute(myThread);
            Thread.sleep(30000);// 10000 + 10000 + 10000 ^^ for each thread
        }
    }
}

輸出:

2019-06-05 12:31:01.549  INFO 68118 --- [xecutor_thread1] com.example.controller.MyThread          : Called from thread + 22
2019-06-05 12:31:01.549  INFO 68118 --- [xecutor_thread1] com.example.controller.MyThread          : Thread info+ default_task_executor_thread1
2019-06-05 12:31:01.549  INFO 68118 --- [xecutor_thread1] com.example.controller.MyThread          : Thread info+ java.lang.ThreadGroup[name=main,maxpri=10]
2019-06-05 12:31:11.552  INFO 68118 --- [xecutor_thread1] com.example.controller.MyThread          : Called from thread + 22
2019-06-05 12:31:11.552  INFO 68118 --- [xecutor_thread1] com.example.controller.MyThread          : Thread info+ default_task_executor_thread1
2019-06-05 12:31:11.552  INFO 68118 --- [xecutor_thread1] com.example.controller.MyThread          : Thread info+ java.lang.ThreadGroup[name=main,maxpri=10]
2019-06-05 12:31:21.554  INFO 68118 --- [xecutor_thread1] com.example.controller.MyThread          : Called from thread + 22
2019-06-05 12:31:21.555  INFO 68118 --- [xecutor_thread1] com.example.controller.MyThread          : Thread info+ default_task_executor_thread1
2019-06-05 12:31:21.555  INFO 68118 --- [xecutor_thread1] com.example.controller.MyThread          : Thread info+ java.lang.ThreadGroup[name=main,maxpri=10]

如果檢查以上配置,由於我們只有1個線程來執行Runnable,因此將使用同一線程來執行所有三個調用。

如果將線程總數更改為兩個,則一次將使用兩個線程來執行Runnable(MyThread)和
一旦一項任務完成,另一項任務將使用先前可運行的線程釋放的同一線程。

executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);

輸出:

2019-06-05 12:39:26.163  INFO 68407 --- [xecutor_thread2] com.example.controller.MyThread          : Called from thread + 23
2019-06-05 12:39:26.163  INFO 68407 --- [xecutor_thread2] com.example.controller.MyThread          : Thread info+ default_task_executor_thread2
2019-06-05 12:39:26.163  INFO 68407 --- [xecutor_thread2] com.example.controller.MyThread          : Thread info+ java.lang.ThreadGroup[name=main,maxpri=10]
2019-06-05 12:39:26.163  INFO 68407 --- [xecutor_thread1] com.example.controller.MyThread          : Called from thread + 22
2019-06-05 12:39:26.164  INFO 68407 --- [xecutor_thread1] com.example.controller.MyThread          : Thread info+ default_task_executor_thread1
2019-06-05 12:39:26.164  INFO 68407 --- [xecutor_thread1] com.example.controller.MyThread          : Thread info+ java.lang.ThreadGroup[name=main,maxpri=10]
2019-06-05 12:39:36.169  INFO 68407 --- [xecutor_thread1] com.example.controller.MyThread          : Called from thread + 22
2019-06-05 12:39:36.169  INFO 68407 --- [xecutor_thread1] com.example.controller.MyThread          : Thread info+ default_task_executor_thread1
2019-06-05 12:39:36.169  INFO 68407 --- [xecutor_thread1] com.example.controller.MyThread          : Thread info+ java.lang.ThreadGroup[name=main,maxpri=10]

我寫了如何創建Thread的答案,我的意思是你應該看一下有關此行的源代碼

taskExecutor.execute(myThread);

    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);//**here newThread create a Thread to the Thread pool,**
    }

在您的代碼名“ MyThread”中,它只是一個Runnable ,Spring僅包含那個Bean,但它只提供了run()方法,因此單個 bean只是將您的代碼吹成一個方法。 並不意味着線程池中的線程是唯一的

@Component
@Scope("singleton")
public class MyThread implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyThread.class);

    @Override
    public void run() {
        LOGGER.info("Called from thread + " + Thread.currentThread().getId());
    }}

這個問題是: 如何創建線程

  1. 擴展Thread

     public class NewThread1 extends Thread { private String name; public NewThread1(String name) { this.name=name; } public void run() { for (int i = 0; i < 5; i++) { System.out.println(name + "running : " + i); try { sleep((int) (Math.random() * 10)); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args){ NewThread1 mTh1=new NewThread1("A"); NewThread1 mTh2=new NewThread1("B"); mTh1.start(); mTh2.start(); } } 
  2. 使用Runnable ,runnable不是一個線程,它可以被許多線程使用。 我的英語不好我希望我能幫助你

     public class MyRunnable implements Runnable{ private String name; MyRunnable(String name){ this.name=name; } public void run() { for (int i = 0; i < 5; i++) { System.out.println(name + "running : " + i); try { Thread.sleep((int) (Math.random() * 10)); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { System.out.println("main thread start running!"); Thread thread1=new Thread(new MyRunnable("A")); thread1.start(); thread1.join(); System.out.println("main thread now is End My dear!"); } 

    }

下面是Thread.class源代碼:

    @Override
    public void run() {
        if (target != null) {
            target.run();//the target is a runnable
        }
    }
  1. 實際上,應該有第三個,一個是實現Callable 接口並將其與Futurethread pool 它將返回每個線程的結果。 如果你想學習,可以用谷歌搜索

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM