【问题标题】:Java: New thread to skip synchronised method if another thread already in itJava:如果另一个线程已经在其中,则新线程跳过同步方法
【发布时间】:2019-06-26 16:05:03
【问题描述】:

要求

  1. 我需要能够通过 POST 调用触发(长时间运行的)作业并立即返回。

  2. 一次只能运行一个线程。

  3. 这项工作是一项昂贵的工作,如果一项工作已经在进行中,我希望该工作的所有未来触发器都不做任何事情。

代码

@RestController
public class SomeTask {

    private SomeService someService;

    @Autowired
    public SomeTask(SomeService someService) {
        this.someService = someService;
    }

    @Async // requirement 1
    @RequestMapping(method = RequestMethod.POST, path = "/triggerJob")
    public void triggerJob() {
        expensiveLongRunningJob();
    }

    /**
     * Synchronized in order to restrict multiple invocations. // requirement 2
     *
     */
    private synchronized void expensiveLongRunningJob() { 
        someService.executedJob();
    }
}

问题

上面的代码要求 1 和 2 都满足了。满足要求 3 的最佳方法是什么(让新线程作为 POST 调用的结果创建,跳过同步方法并在获取锁失败时立即返回)?

【问题讨论】:

  • 我已经更新了问题部分。
  • 如果一个工作开始并且已经完成,触发器应该做些什么吗?如果不是,则意味着该作业仅在一生中执行-您真的是这个意思吗?如果是,那肯定是糟糕的设计——一毫秒前不启动,现在启动——但实际上并没有发生根本性的变化。
  • @AlexeiKaigorodov 如果一项作业已启动并处理,则应忽略并发调用。一旦当前工作完成,就可以处理新的请求。简而言之,一次只运行一个作业 + 请求不会排队/阻塞。

标签: java spring multithreading


【解决方案1】:

同步并不是这项工作的正确工具。你可以这样做:

@RestController
public class SomeTask {

    private SomeService someService;
    private final AtomicBoolean isTriggered = new AtomicBoolean();

    @Autowired
    public SomeTask(SomeService someService) {
        this.someService = someService;
    }

    @Async // requirement 1
    @RequestMapping(method = RequestMethod.POST, path = "/triggerJob")
    public void triggerJob() {
        if (!isTriggered.getAndSet(true)) {
            try {
                expensiveLongRunningJob();
            } finally {
                isTriggered.set(false);
            }
        }
    }

    /**
     * only runs once at a time, in the thread that sets isTriggered to true
     */
    private void expensiveLongRunningJob() { 
        someService.executedJob();
    }
}

【讨论】:

  • 我似乎无法为 triggerJob() 方法编写单元测试来测试只有一个线程进入并运行作业而在此期间启动的所有其他线程都被跳过的事实,而不使用一些我的测试中有某种延迟(Thread.sleep)。有什么想法吗?
  • 在您的单元测试中,您的“长时间运行的作业”可以等待您在其他线程完成时释放的 CountDownLatch
【解决方案2】:

对于要求1,如果你只想使用@Async,你应该在服务方法而不是控制器方法上使用它。但请注意,通过使其异步,您将失去对作业的控制,并且无法处理故障,除非您使用 Future 实现 @Async 并通过实现 AsyncUncaughtExceptionHandler 接口来处理故障。

对于要求 3,您可以在服务中设置一个可变布尔字段,该字段在作业流程开始之前设置,在作业流程完成后取消设置。在您的控制器方法中,您可以检查服务的 volatile 布尔字段以确定作业是否正在执行,如果作业正在进行,则返回适当的消息。另外,请确保在处理AsyncUncaughtExceptionHandler接口实现失败时取消设置布尔字段。

服务:

@Service
public class SomeService {

    public volatile boolean isJobInProgress = false;

    @Async
    public Future<String> executeJob() {
        isJobInProgress = true;
        //Job processing logic
        isJobInProgress = false;
    }
}

控制器:

@RestController
public class SomeTask {

    @Autowired
    private SomeService someService;

    @RequestMapping(method = RequestMethod.POST, path = "/triggerJob")
    public void triggerJob() {
        if (!someService.isJobInProgress){
            someService.executeJob(); //can have this in a sync block to be on the safer side. 
        } else {
            return;
        }
    }

}

AsyncUncaughtExceptionHandler 的实现:

public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Autowired
    private SomeService someService;

    @Override
    public void handleUncaughtException(
            Throwable throwable, Method method, Object... obj) {

        //Handle failure
        if (someService.isJobInProgress){
            someService.isJobInProgress = false;
        }
    }
}

@Async 配置:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        return new ThreadPoolTaskExecutor();
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多