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

时间:2019-06-05 04:56:42

标签: java spring-boot

我正在使用由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

3 个答案:

答案 0 :(得分:0)

我认为,如您在问题中所述,可以更好地解释单例的概念:

有一个应用程序上下文-如果需要,可以显示所有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可以完美地在多线程环境中工作,例如,控制器可以由不同的客户端同时调用(因此,不同的线程,因为每个请求模型都是线程)。

这与多线程无关,而与注入有关。

答案 1 :(得分:0)

@BoristheSpider的希望评论消除了您对Thread和Runnable的怀疑。
关于单例,this answer将帮助您了解更多信息。
我将尝试回答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,因此同一线程将用于执行所有三个调用。

如果将线程总数更改为2,则一次将使用两个线程来执行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]

答案 2 :(得分:-3)

我写了如何创建@Column的答案,这意味着您应该查看有关此行的源代码

Thread

taskExecutor.execute(myThread);

在您的代码名“ 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,** } ,Spring仅包含那个Bean,但是它仅提供了一个Runnable方法, 因此单个 bean只是一种方法而使您的代码崩溃。并不意味着线程池中的线程是唯一的

run()

这个问题是: 如何创建线程

  1. 扩展@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()); }}

    Thread
  2. 使用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(); } } ,runnable不是线程,可以由 许多线程。我的英语不好,希望我能帮到你

    Runnable

    }

以下是Thread.class源代码:

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!");
}
  1. 实际上,应该有第三个,一个是实现 @Override public void run() { if (target != null) { target.run();//the target is a runnable } } 接口,并将其与CallableFuture一起使用。它将返回每个线程的结果。如果您想学习,可以用Google搜索