введение
Асинхронное программирование Java значительно экономит время выполнения основной программы и повышает эффективность использования вычислительных ресурсов, что является одним из важнейших навыков старших инженеров Java. В этой статье основное внимание уделяется тому, что такое асинхронность, какие проблемы решает асинхронность и как программировать асинхронно.
что такое асинхронный
Прежде чем объяснять асинхронное программирование, давайте посмотрим на определение синхронного программирования. Синхронное программирование представляет собой типичную модель «запрос-ответ».После того, как запрос вызывает функцию или метод, необходимо дождаться возврата своего ответа, а затем выполнить последующий код. Самая большая особенность синхронизации заключается в том, что "аккуратный", при выполнении каждого процесса последний возвращает результат. Как показано
Асинхронное программирование только отправляет инструкцию вызова, и вызывающей стороне не нужно ждать завершения выполнения вызываемого метода, а продолжает выполнять следующий процесс. В многопроцессорной или многоядерной среде асинхронные вызовы действительно выполняются параллельно. Как показано
Какую проблему решает асинхронность
Целью асинхронного программирования на Java является полное использование ресурсов ЦП компьютера и недопущение блокировки основной программы при выполнении длительной задачи, тем самым оптимизируя время выполнения основной программы. Такими трудоемкими задачами могут быть операции ввода-вывода, удаленные вызовы и вычислительные задачи с высокой плотностью.
Без многопоточного асинхронного программирования наша система блокировалась бы на времязатратных подзадачах, что значительно увеличило бы время выполнения основной функциональной задачи. Java и предоставляет богатый API для выполнения многопоточного асинхронного программирования. Из NIO, Future, CompletableFuture, Fork/Join и parallelStream. Кроме того, платформа guava от Google предоставляет ListenableFuture и Spring @Async для упрощения асинхронного программирования.
Как программировать асинхронно
В этой статье будет использоваться наиболее часто используемый Spring @Async для иллюстрации асинхронного программирования.
@Async асинхронный вызов
- @Async также реализуется через АОП (аспект), как и @Transactional.
- Метод, аннотированный @Async, должен быть общедоступным. Поскольку сущностью АОП является динамический прокси, динамический прокси требует, чтобы методы были общедоступными.
- @Async должен вызываться между классами, причина в том, что прямые вызовы одного и того же типа не могут быть проксированы динамически.
- Нужно добавить аннотацию @EnableAsync
Асинхронные вызовы @Async разделены на два: один — вызов без возврата значения, другой — вызов с возвращаемым значением. Давайте сначала рассмотрим простейший невозвратный вызов значения.
TestAsyncService вызывает функцию testAsyncSimple в AsyncService.
public class TestAsyncService {
private final AsyncService asyncService;
public TestAsyncService(AsyncService asyncService) {
this.asyncService = asyncService;
}
public void testAsyncSimple() throws InterruptedException {
System.out.println("-----start-----");
asyncService.testAsyncSimple();
System.out.println("-----end-----");
}
}
@Async
public void testAsyncSimple() throws InterruptedException {
Thread.sleep(1000);
System.out.println("async success");
}
модульный тест
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
class TestAsyncServiceTest {
@Autowired
private TestAsyncService testAsyncService;
@Test
void testAsyncSimple() throws InterruptedException {
testAsyncService.testAsyncSimple();
Thread.sleep(5000); //阻塞测试用例,等待异步调用结束
}
}
Результат теста end распечатывается, чтобы показать, что основная программа завершила работу. И конец печатается перед асинхронным успехом, указывая на то, что функция testAsyncSimple действительно выполнена асинхронно.
-----start-----
-----end-----
-----async success-----
Необходимо использовать вызов с возвращаемым значениемFuture
своего рода. Проще говоря, класс Future представляет будущий результат асинхронного вычисления — этот результат в конечном итоге появится в Future после завершения обработки. Другими словами, будущее означаетПолучить результат выполнения в какой-то момент в будущем, тип возвращаемых данных можно настроить.
Давайте начнем с определения асинхронной функции, которая будет возвращать строку «hello world» через 3 секунды.
@Async
public Future<String> testAsyncWithResult() throws InterruptedException {
Thread.sleep(3000);
return new AsyncResult<String>("hello world !!!!");
}
Определено в тестовой функцииFuture<String>
Принять «привет мир» вернулся в какой-то момент в будущем.while (true)
блокирующий поток в ожиданииFuture
возвращение,future.isDone()
Подтвердите завершение асинхронного потока, если он завершен, с помощьюfuture.get()
Получите возвращаемое значение. Конечно, вы также можете получитьexception
аномальный.
public String testAsyncWithResult() throws ExecutionException, InterruptedException {
Future<String> future = asyncService.testAsyncWithResult();
while (true) {
if (future.isDone()) {
System.out.println("Result from asynchronous process - " + future.get());
return future.get();
}
System.out.println("Continue doing something else. ");
}
}
@Асинхронный пул потоков
По умолчанию Spring используетSimpleAsyncTaskExecutor
Инициализируйте пул потоков. Каждый раз, когда задача, переданная ему клиентом, выполняется, он запускает новый поток и позволяет разработчикам контролировать верхний предел параллельных потоков (concurrencyLimit), тем самым играя определенную роль в регулировании ресурсов. По умолчанию значение concurrencyLimit равно -1, то есть регулирование ресурсов не включено.
@Async
public void testAsyncSimple() {}
@Async также поддерживает использование указанного пула потоков.Вы можете указать различные параметры пула потоков следующим образом.
@Configuration
@EnableAsync
public class ThreadPoolConfig {
@Bean(ConfigConstant.TEST_EXECUTOR)
@Primary
public TaskExecutor testTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
initTaskExecutor(ConfigConstant.TEST_EXECUTOR, executor);
return executor;
}
private ThreadPoolTaskExecutor initTaskExecutor(String name, ThreadPoolTaskExecutor executor) {
executor.setThreadNamePrefix(name + "-task-");
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(1000);
/* 60 seconds */
executor.setKeepAliveSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
}
@Async использует указанный пример пула потоков
@Async(ConfigConstant.TEST_EXECUTOR)
public void testAsyncWithExecutor() {}
@Асинхронная транзакция
- @Transactional вызывает подфункции с помощью @Async, транзакция не вступает в силу
- @Async вызывает подфункцию с @Transactional, транзакция вступает в силу
Давайте сначала поговорим о первом пункте, если @Transactional выполняется до аспекта @Async, но поскольку управление транзакциями Spring зависит отThreadLocal
, поэтому транзакция не воспринимается в открытом асинхронном потоке.Фильм в том, что после того, как Spring откроет транзакцию, он установит соединение с текущим потоком, но в это время открывается новый поток.При выполнении собственно SQL код, получается через ThreadLocal Новое соединение будет открыто, если оно не подключено, и не будет установленоautoCommit
, поэтому у функции в целом не будет транзакций.
Таким образом, легко понять второй момент: @Async выполняется первым, а ThreadLocal, используемый @Transactional, по-прежнему является ThreadLocal в новом потоке, открытом @Async, поэтому транзакция вступает в силу.
@Асинхронная обработка исключений
Существует два типа обработки исключений: отсутствие возвращаемого значения и возвращаемое значение.
- нет возвращаемого значения путем реализации
AsyncConfigurer
интерфейс для обработки исключений - Существует возвращаемое значение через
Future.get()
Возвратите исключение и поймайте его с помощью обычной попытки... поймать
Сначала посмотрите на случай отсутствия возвращаемого значения, определите класс конфигурации захвата исключений.AsyncExceptionConfig
, реализация метода SpringAsyncExceptionHandler определена в классе конфигурацииAsyncUncaughtExceptionHandler
интерфейс.
@Configuration
public class AsyncExceptionConfig implements AsyncConfigurer {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SpringAsyncExceptionHandler();
}
class SpringAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
System.out.println("------我是Async无返回方法的异常处理方法---------");
}
}
}
Мы запускаем тест RuntimeException() в асинхронном коде
@Async
public void testAsyncWithException() throws InterruptedException {
Thread.sleep(1000);
throw new RuntimeException();
}
Результаты теста
-----start-----
-----end-----
------我是Async无返回方法的异常处理方法---------
Существует возвращаемое значение, которое возвращает исключение через Future.get(), а TestService вызывает указанный выше код исключения.
public void testAsyncWithException() throws ExecutionException, InterruptedException {
System.out.println("start");
Future<String> future = asyncService.testAsyncWithException();
while (true) {
if (future.isDone()) {
System.out.println("Result from asynchronous process - " + future.get());
System.out.println("end");
}
System.out.println("Continue doing something else. ");
}
}
Результат теста, выброшенное исключение, может быть захвачен и обработан с помощью обычного try...catch.
Continue doing something else.
Continue doing something else.
Continue doing something else.
Continue doing something else.
java.util.concurrent.ExecutionException: java.lang.RuntimeException
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.example.springbootexample.service.AsyncService.TestAsyncService.testAsyncWithException(TestAsyncService.java:54)
at com.example.springbootexample.service.AsyncService.TestAsyncServiceTest.testAsyncWithException(TestAsyncServiceTest.java:50)
Суммировать
Асинхронное программирование на Java значительно экономит время выполнения основной программы, предотвращая блокировку основной программы при выполнении длительной задачи, тем самым оптимизируя время выполнения основной программы. В этой статье представлено асинхронное программирование Java из наиболее часто используемого Spring @Async.
Асинхронные функции должны быть общедоступными, и их можно вызывать в разных классах, чтобы они вступили в силу. Вызов делится на два случая: отсутствие возвращаемого значения и возвращаемое значение. Существует возвращаемое значение для принятия возвращаемого значения через класс Future. Используйте Future.isDone(), чтобы подтвердить, завершается ли асинхронный поток.
Асинхронный пул потоков может использовать пул потоков по умолчанию или инициализировать пользовательский пул потоков.
Что касается асинхронных транзакций, @Async вызывает подфункцию с @Transactional, и транзакция вступает в силу. @Transactional вызывает подфункции с помощью @Async, транзакция не вступает в силу.
С точки зрения обработки исключений он также делится на два случая: отсутствие возвращаемого значения и возвращаемое значение. Нельзя определить возвращаемое значение для захвата класса конфигурации исключений AsyncExceptionConfig для обработки исключений. Если есть возвращаемое значение, через Future.get() может быть выброшено исключение, а затем try...catch сможет его нормально перехватить.
Ссылаться на
Из аннотаций Transactional и Async: https://juejin.cn/post/6844903928799182861
В этой статье используетсяmdniceнабор текста